-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Redis durablejobs storage #9823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jaironalves
wants to merge
40
commits into
dotnet:main
Choose a base branch
from
jaironalves:feature/redis-durablejobs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 33 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
7b93b58
feat: 2 versions - Need Refactor
jaironalves b98ac4e
feat: Redis Durable Jobs
jaironalves bcb6316
Initial analysis of Redis DurableJobs package
Copilot 42f12ed
Make Redis DurableJobs production-ready with proper hosting infrastru…
Copilot 308c6ad
Add Redis DurableJobs tests following Azure test patterns
Copilot 73fb231
Update RedisJobShardOptions to use ConfigurationOptions pattern and r…
Copilot e98c0ba
Add missing using Orleans.Configuration namespace in RedisDurableJobs…
Copilot 451a6e5
Remove nullable type annotations from test fixture (nullable not enab…
Copilot 15d9134
Fix StreamRange count parameter - remove invalid 0 value
Copilot 833aa43
Remove unnecessary note comment from StreamRange call
Copilot 4316469
Add Encode method to RedisStreamJsonSerializer and use JobOperationJs…
Copilot c358bdf
Fix UnregisterShardAsync to delete shard when job count is zero like …
Copilot e4f62f8
Set Metadata property in RedisJobShard constructor to sync with Azure…
Copilot b616734
Fix CreateShardAsync to save all metadata fields using cjson in Lua s…
Copilot b730c36
Remove new Metadata property - use base class property instead
Copilot 088b209
feat: Revert lines projects positions
jaironalves 953c954
Add LoggerMessage attributes to RedisJobShardManager
Copilot c6c3dda
Merge branch 'dotnet:main' into feature/redis-durablejobs
jaironalves 5f7051d
Remove unused log methods with Azure-specific references from Redis D…
Copilot 32e80c3
Fix critical issues in Redis DurableJobs implementation
Copilot b2b7c2b
Fix metadata version calculation bug in Redis DurableJobs
Copilot a4e925a
Add Portuguese summary of Redis DurableJobs review
Copilot 61a989e
Remove review markdown files as requested
Copilot 7ee6d6d
Centralize Redis operations into RedisOperationsManager for DurableJo…
Copilot 6c8f389
Fix method signature in TryTakeOwnershipAsync call
Copilot 1fbda00
Add clarifying comments for design decisions
Copilot e410213
feat: Fix Encode Return Type
jaironalves 5856f47
feat: Review unused var
jaironalves d7eb627
Apply PR review suggestions: use 'is null/is not null' pattern and nu…
Copilot 2013565
Fix code formatting to follow project guidelines
Copilot 978b9b2
Validate CreateMultiplexer option
jaironalves c369cc2
feat: Copilot Review
jaironalves 5f49aec
fix: Move redis checker to constructor - Same other redis implementat…
jaironalves 4ecab6b
feat: Extensions remove duplicates
jaironalves f391104
feat: Copilot Review
jaironalves 671c9a9
feat: Key prefix redis options
jaironalves 841de00
feat: Merge main
jaironalves e9f0de9
Update test project to match the new project layout
jaironalves 24c23ad
Add internal visible
jaironalves 72ce1f9
Remove properties
jaironalves File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
71 changes: 71 additions & 0 deletions
71
src/Redis/Orleans.DurableJobs.Redis/Hosting/RedisDurableJobsExtensions.cs
This file contains hidden or 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,71 @@ | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Options; | ||
| using Orleans.Configuration; | ||
| using Orleans.Configuration.Internal; | ||
| using Orleans.DurableJobs; | ||
| using Orleans.DurableJobs.Redis; | ||
|
|
||
| namespace Orleans.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Extensions for configuring Redis durable jobs. | ||
| /// </summary> | ||
| public static class RedisDurableJobsExtensions | ||
| { | ||
| /// <summary> | ||
| /// Adds durable jobs storage backed by Redis. | ||
| /// </summary> | ||
| /// <param name="builder">The builder.</param> | ||
| /// <param name="configure">The delegate used to configure the durable jobs storage.</param> | ||
| /// <returns>The provided <see cref="ISiloBuilder"/>, for chaining.</returns> | ||
| public static ISiloBuilder UseRedisDurableJobs(this ISiloBuilder builder, Action<RedisJobShardOptions> configure) | ||
| { | ||
| builder.ConfigureServices(services => services.UseRedisDurableJobs(configure)); | ||
| return builder; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds durable jobs storage backed by Redis. | ||
| /// </summary> | ||
| /// <param name="builder">The builder.</param> | ||
| /// <param name="configureOptions">The configuration delegate.</param> | ||
| /// <returns>The provided <see cref="ISiloBuilder"/>, for chaining.</returns> | ||
| public static ISiloBuilder UseRedisDurableJobs(this ISiloBuilder builder, Action<OptionsBuilder<RedisJobShardOptions>> configureOptions) | ||
| { | ||
| builder.ConfigureServices(services => services.UseRedisDurableJobs(configureOptions)); | ||
| return builder; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds durable jobs storage backed by Redis. | ||
| /// </summary> | ||
| /// <param name="services">The service collection.</param> | ||
| /// <param name="configure">The delegate used to configure the durable jobs storage.</param> | ||
| /// <returns>The provided <see cref="IServiceCollection"/>, for chaining.</returns> | ||
| public static IServiceCollection UseRedisDurableJobs(this IServiceCollection services, Action<RedisJobShardOptions> configure) | ||
| { | ||
| services.AddDurableJobs(); | ||
| services.AddSingleton<RedisJobShardManager>(); | ||
| services.AddFromExisting<JobShardManager, RedisJobShardManager>(); | ||
| services.Configure(configure); | ||
| services.ConfigureFormatter<RedisJobShardOptions>(); | ||
| return services; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds durable jobs storage backed by Redis. | ||
| /// </summary> | ||
| /// <param name="services">The service collection.</param> | ||
| /// <param name="configureOptions">The configuration delegate.</param> | ||
| /// <returns>The provided <see cref="IServiceCollection"/>, for chaining.</returns> | ||
| public static IServiceCollection UseRedisDurableJobs(this IServiceCollection services, Action<OptionsBuilder<RedisJobShardOptions>>? configureOptions) | ||
| { | ||
| services.AddDurableJobs(); | ||
| services.AddSingleton<RedisJobShardManager>(); | ||
| services.AddFromExisting<JobShardManager, RedisJobShardManager>(); | ||
| configureOptions?.Invoke(services.AddOptions<RedisJobShardOptions>()); | ||
| services.ConfigureFormatter<RedisJobShardOptions>(); | ||
| services.AddTransient<IConfigurationValidator>(sp => new RedisJobShardOptionsValidator(sp.GetRequiredService<IOptionsMonitor<RedisJobShardOptions>>().Get(Options.DefaultName), Options.DefaultName)); | ||
| return services; | ||
| } | ||
| } | ||
66 changes: 66 additions & 0 deletions
66
src/Redis/Orleans.DurableJobs.Redis/Hosting/RedisJobShardOptions.cs
This file contains hidden or 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 StackExchange.Redis; | ||
|
|
||
| namespace Orleans.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Options for configuring the Redis durable jobs provider. | ||
| /// </summary> | ||
| public class RedisJobShardOptions | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the Redis client configuration. | ||
| /// </summary> | ||
| [RedactRedisConfigurationOptions] | ||
| public ConfigurationOptions? ConfigurationOptions { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets a delegate to create the Redis connection multiplexer. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This delegate is called once during initialization to create the connection. | ||
| /// </remarks> | ||
| public Func<RedisJobShardOptions, Task<IConnectionMultiplexer>> CreateMultiplexer { get; set; } = DefaultCreateMultiplexer; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the prefix for shard identifiers. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This prefix is used to namespace shards in Redis, allowing multiple applications to share the same Redis instance. | ||
| /// </remarks> | ||
| public string ShardPrefix { get; set; } = "shard"; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the maximum number of retries when creating a shard in case of ID collisions. | ||
| /// </summary> | ||
| public int MaxShardCreationRetries { get; set; } = 5; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the maximum number of job operations to batch together in a single write. | ||
| /// Default is 128 operations. | ||
| /// </summary> | ||
| public int MaxBatchSize { get; set; } = 128; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the minimum number of job operations to batch together before flushing. | ||
| /// Default is 1 operation (immediate flush, optimized for latency). | ||
| /// </summary> | ||
| public int MinBatchSize { get; set; } = 1; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the maximum time to wait for additional operations if the minimum batch size isn't reached | ||
| /// before flushing a batch. | ||
| /// Default is 100 milliseconds. | ||
| /// </summary> | ||
| public TimeSpan BatchFlushInterval { get; set; } = TimeSpan.FromMilliseconds(100); | ||
|
|
||
| /// <summary> | ||
| /// The default multiplexer creation delegate. | ||
| /// </summary> | ||
| public static async Task<IConnectionMultiplexer> DefaultCreateMultiplexer(RedisJobShardOptions options) | ||
| => await ConnectionMultiplexer.ConnectAsync(options.ConfigurationOptions!); | ||
| } | ||
|
|
||
| internal class RedactRedisConfigurationOptions : RedactAttribute | ||
| { | ||
| public override string Redact(object value) => value is ConfigurationOptions cfg ? cfg.ToString(includePassword: false) : base.Redact(value); | ||
| } |
64 changes: 64 additions & 0 deletions
64
src/Redis/Orleans.DurableJobs.Redis/Hosting/RedisJobShardOptionsValidator.cs
This file contains hidden or 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 @@ | ||
| namespace Orleans.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Validates <see cref="RedisJobShardOptions"/>. | ||
| /// </summary> | ||
| public class RedisJobShardOptionsValidator : IConfigurationValidator | ||
| { | ||
| private readonly RedisJobShardOptions _options; | ||
| private readonly string _name; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="RedisJobShardOptionsValidator"/> class. | ||
| /// </summary> | ||
| /// <param name="options">The options.</param> | ||
| /// <param name="name">The name.</param> | ||
| public RedisJobShardOptionsValidator(RedisJobShardOptions options, string name) | ||
| { | ||
| _options = options; | ||
| _name = name; | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public void ValidateConfiguration() | ||
| { | ||
| if (_options.ConfigurationOptions is null) | ||
| { | ||
| throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisJobShardOptions)} with name '{_name}'. {nameof(_options.ConfigurationOptions)} is required."); | ||
| } | ||
|
|
||
jaironalves marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (_options.CreateMultiplexer is null) | ||
| { | ||
| throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisJobShardOptions)} with name '{_name}'. {nameof(_options.CreateMultiplexer)} is required."); | ||
| } | ||
| if (string.IsNullOrWhiteSpace(_options.ShardPrefix)) | ||
| { | ||
| throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisJobShardOptions)} with name '{_name}'. {nameof(_options.ShardPrefix)} is required."); | ||
| } | ||
|
|
||
| if (_options.MaxShardCreationRetries < 1) | ||
| { | ||
| throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisJobShardOptions)} with name '{_name}'. {nameof(_options.MaxShardCreationRetries)} must be at least 1."); | ||
| } | ||
|
|
||
| if (_options.MaxBatchSize < 1) | ||
| { | ||
| throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisJobShardOptions)} with name '{_name}'. {nameof(_options.MaxBatchSize)} must be at least 1."); | ||
| } | ||
|
|
||
| if (_options.MinBatchSize < 1) | ||
| { | ||
| throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisJobShardOptions)} with name '{_name}'. {nameof(_options.MinBatchSize)} must be at least 1."); | ||
| } | ||
|
|
||
| if (_options.MinBatchSize > _options.MaxBatchSize) | ||
| { | ||
| throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisJobShardOptions)} with name '{_name}'. {nameof(_options.MinBatchSize)} must not exceed {nameof(_options.MaxBatchSize)}."); | ||
| } | ||
|
|
||
| if (_options.BatchFlushInterval < TimeSpan.Zero) | ||
| { | ||
| throw new OrleansConfigurationException($"Invalid configuration for {nameof(RedisJobShardOptions)} with name '{_name}'. {nameof(_options.BatchFlushInterval)} must not be negative."); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or 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,106 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace Orleans.DurableJobs.Redis; | ||
|
|
||
| /// <summary> | ||
| /// Represents an operation to be performed on a durable job. | ||
| /// </summary> | ||
| internal readonly struct JobOperation | ||
jaironalves marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| /// <summary> | ||
| /// The type of operation to perform. | ||
| /// </summary> | ||
| public enum OperationType | ||
| { | ||
| Add, | ||
| Remove, | ||
| Retry, | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the type of operation. | ||
| /// </summary> | ||
| public OperationType Type { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the job identifier. | ||
| /// </summary> | ||
| public string Id { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the job name (only used for Add operations). | ||
| /// </summary> | ||
| public string? Name { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the due time (used for Add and Retry operations). | ||
| /// </summary> | ||
| public DateTimeOffset? DueTime { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the target grain ID (only used for Add operations). | ||
| /// </summary> | ||
| public GrainId? TargetGrainId { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the job metadata (only used for Add operations). | ||
| /// </summary> | ||
| public IReadOnlyDictionary<string, string>? Metadata { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Creates an Add operation for scheduling a new job. | ||
| /// </summary> | ||
| /// <param name="id">The job identifier.</param> | ||
| /// <param name="name">The job name.</param> | ||
| /// <param name="dueTime">The job due time.</param> | ||
| /// <param name="targetGrainId">The target grain ID.</param> | ||
| /// <param name="metadata">The job metadata.</param> | ||
| /// <returns>A new JobOperation for adding a job.</returns> | ||
| /// <exception cref="ArgumentException">Thrown when <paramref name="id"/> or <paramref name="name"/> is null or empty.</exception> | ||
| public static JobOperation CreateAddOperation(string id, string name, DateTimeOffset dueTime, GrainId targetGrainId, IReadOnlyDictionary<string, string>? metadata) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(id); | ||
| ArgumentException.ThrowIfNullOrEmpty(name); | ||
|
|
||
| return new() { Type = OperationType.Add, Id = id, Name = name, DueTime = dueTime, TargetGrainId = targetGrainId, Metadata = metadata }; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a Remove operation for canceling a job. | ||
| /// </summary> | ||
| /// <param name="id">The job identifier.</param> | ||
| /// <returns>A new JobOperation for removing a job.</returns> | ||
| /// <exception cref="ArgumentException">Thrown when <paramref name="id"/> is null or empty.</exception> | ||
| public static JobOperation CreateRemoveOperation(string id) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(id); | ||
|
|
||
| return new() { Type = OperationType.Remove, Id = id }; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates a Retry operation for rescheduling a job. | ||
| /// </summary> | ||
| /// <param name="id">The job identifier.</param> | ||
| /// <param name="dueTime">The new due time.</param> | ||
| /// <returns>A new JobOperation for retrying a job.</returns> | ||
| /// <exception cref="ArgumentException">Thrown when <paramref name="id"/> is null or empty.</exception> | ||
| public static JobOperation CreateRetryOperation(string id, DateTimeOffset dueTime) | ||
| { | ||
| ArgumentException.ThrowIfNullOrEmpty(id); | ||
|
|
||
| return new() { Type = OperationType.Retry, Id = id, DueTime = dueTime }; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// JSON serialization context for JobOperation with compile-time source generation. | ||
| /// </summary> | ||
| [JsonSerializable(typeof(JobOperation))] | ||
| [JsonSourceGenerationOptions( | ||
| DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, | ||
| PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, | ||
| WriteIndented = false)] | ||
| internal partial class JobOperationJsonContext : JsonSerializerContext | ||
| { | ||
| } | ||
29 changes: 29 additions & 0 deletions
29
src/Redis/Orleans.DurableJobs.Redis/Orleans.DurableJobs.Redis.csproj
This file contains hidden or 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,29 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <PackageReadmeFile>README.md</PackageReadmeFile> | ||
| <PackageId>Microsoft.Orleans.DurableJobs.Redis</PackageId> | ||
| <Title>Microsoft Orleans Redis Durable Jobs Provider</Title> | ||
| <Description>Microsoft Orleans durable jobs provider backed by Redis</Description> | ||
| <PackageTags>$(PackageTags) Redis</PackageTags> | ||
| <TargetFrameworks>$(DefaultTargetFrameworks)</TargetFrameworks> | ||
| <AssemblyName>Orleans.DurableJobs.Redis</AssemblyName> | ||
| <RootNamespace>Orleans.DurableJobs.Redis</RootNamespace> | ||
| <OrleansBuildTimeCodeGen>true</OrleansBuildTimeCodeGen> | ||
| <DefineConstants>$(DefineConstants)</DefineConstants> | ||
| <Nullable>enable</Nullable> | ||
| <VersionSuffix Condition="$(VersionSuffix) != ''">$(VersionSuffix).alpha.1</VersionSuffix> | ||
| <VersionSuffix Condition="$(VersionSuffix) == ''">alpha.1</VersionSuffix> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="$(SourceRoot)src\Orleans.Runtime\Orleans.Runtime.csproj" /> | ||
| <ProjectReference Include="$(SourceRoot)src\Orleans.DurableJobs\Orleans.DurableJobs.csproj" /> | ||
| <PackageReference Include="StackExchange.Redis" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <None Include="README.md" Pack="true" PackagePath="\" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
3 changes: 3 additions & 0 deletions
3
src/Redis/Orleans.DurableJobs.Redis/Properties/AssemblyInfo.cs
This file contains hidden or 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,3 @@ | ||
| using System.Runtime.CompilerServices; | ||
|
|
||
| [assembly: InternalsVisibleTo("Tester.Redis")] |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.