Skip to content

fix(redis)!: fix multiplexer ownership#10146

Merged
ReubenBond merged 4 commits into
dotnet:mainfrom
ReubenBond:reubenbond/fix-redis-multiplexer-ownership
Jun 4, 2026
Merged

fix(redis)!: fix multiplexer ownership#10146
ReubenBond merged 4 commits into
dotnet:mainfrom
ReubenBond:reubenbond/fix-redis-multiplexer-ownership

Conversation

@ReubenBond

@ReubenBond ReubenBond commented May 28, 2026

Copy link
Copy Markdown
Member

Fixes Redis provider ownership handling for shared IConnectionMultiplexer instances.

Redis providers can be configured with a ServiceKey to use a DI-provided multiplexer. Those instances are shared and owned by DI, so providers should not close or dispose them.

This updates the non-streaming Redis providers to report whether created multiplexers are shared, marks ServiceKey-resolved multiplexers as shared, and implements standard Dispose/DisposeAsync cleanup so owned multiplexers are disposed naturally when the container is disposed while shared multiplexers are left alone.

@ReubenBond ReubenBond changed the title Fix Redis multiplexer ownership fix(redis)!: fix multiplexer ownership May 28, 2026
@ReubenBond ReubenBond requested a review from Copilot May 30, 2026 00:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Redis provider connection ownership handling so DI-provided IConnectionMultiplexer instances are treated as shared and not disposed by Orleans providers.

Changes:

  • Changes Redis provider CreateMultiplexer delegates to return both the multiplexer and an ownership/shared flag.
  • Marks ServiceKey-resolved multiplexers as shared across clustering, reminders, persistence, and grain directory providers.
  • Adds disposal paths for owned multiplexers and updates Redis public API baselines.
Show a summary per file
File Description
src/Redis/Orleans.Reminders.Redis/Storage/RedisReminderTable.cs Adds tracked shared/owned multiplexer disposal.
src/Redis/Orleans.Reminders.Redis/Providers/RedisReminderTableOptions.cs Updates multiplexer factory contract with ownership metadata.
src/Redis/Orleans.Reminders.Redis/Hosting/RedisRemindersProviderBuilder.cs Marks keyed DI multiplexers as shared.
src/Redis/Orleans.Persistence.Redis/Storage/RedisGrainStorage.cs Adds disposal and removes lifecycle close callback.
src/Redis/Orleans.Persistence.Redis/Providers/RedisStorageOptions.cs Updates storage multiplexer factory contract.
src/Redis/Orleans.Persistence.Redis/Hosting/RedisGrainStorageProviderBuilder.cs Marks keyed DI multiplexers as shared.
src/Redis/Orleans.GrainDirectory.Redis/RedisGrainDirectory.cs Adds shared/owned multiplexer disposal for grain directory.
src/Redis/Orleans.GrainDirectory.Redis/Options/RedisGrainDirectoryOptions.cs Updates grain directory multiplexer factory contract.
src/Redis/Orleans.GrainDirectory.Redis/Hosting/RedisGrainDirectoryProviderBuilder.cs Marks keyed DI multiplexers as shared.
src/Redis/Orleans.Clustering.Redis/Storage/RedisMembershipTable.cs Adds async disposal and shared multiplexer tracking.
src/Redis/Orleans.Clustering.Redis/Providers/RedisClusteringOptions.cs Updates clustering multiplexer factory contract.
src/Redis/Orleans.Clustering.Redis/Hosting/RedisClusteringProviderBuilder.cs Marks keyed DI multiplexers as shared for silo/client clustering.
src/api/Redis/Orleans.Reminders.Redis/Orleans.Reminders.Redis.cs Updates generated API baseline for reminder options.
src/api/Redis/Orleans.Persistence.Redis/Orleans.Persistence.Redis.cs Updates generated API baseline for storage disposal/options.
src/api/Redis/Orleans.GrainDirectory.Redis/Orleans.GrainDirectory.Redis.cs Updates generated API baseline for directory disposal/options.
src/api/Redis/Orleans.Clustering.Redis/Orleans.Clustering.Redis.cs Updates generated API baseline for clustering options.

Copilot's findings

  • Files reviewed: 16/16 changed files
  • Comments generated: 2

Comment thread src/Redis/Orleans.GrainDirectory.Redis/RedisGrainDirectory.cs
Comment thread src/Redis/Orleans.GrainDirectory.Redis/RedisGrainDirectory.cs
ReubenBond and others added 3 commits May 29, 2026 19:49
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@ReubenBond ReubenBond force-pushed the reubenbond/fix-redis-multiplexer-ownership branch from 394768a to 326095a Compare May 30, 2026 02:49
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ReubenBond ReubenBond requested a review from Copilot June 4, 2026 20:59
@ReubenBond ReubenBond added this pull request to the merge queue Jun 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 16/16 changed files
  • Comments generated: 3

Comment on lines +192 to 196
public void Dispose()
{
if (_redis != null && _redis.IsConnected)
var redis = _redis;
if (redis is null)
{

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 0d58300: _disposed is now set at the very start of Dispose(), before the early
eturn when _redis is null. So disposing before (or without) Initialize leaves the instance in a disposed state and later Lookup calls return null instead of throwing a NullReferenceException.

Comment on lines +216 to 238
public async ValueTask DisposeAsync()
{
var redis = _redis;
if (redis is null)
{
return;
}

var redisIsShared = _redisIsShared;
redis.ConnectionRestored -= LogConnectionRestored;
redis.ConnectionFailed -= LogConnectionFailed;
redis.ErrorMessage -= LogErrorMessage;
redis.InternalError -= LogInternalError;
_disposed = true;
_redis = null!;
_database = null!;
_redisIsShared = false;

if (!redisIsShared)
{
await redis.DisposeAsync().ConfigureAwait(false);
}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 0d58300: same change applied to DisposeAsync() — _disposed is set before the early return so the object behaves consistently as disposed even when initialization never completed.

Comment on lines 72 to +76
try
{
LogDebugInitializing(_name, _serviceId, _options.DeleteStateOnClear);

_connection = await _options.CreateMultiplexer(_options).ConfigureAwait(false);
(_connection, _connectionIsShared) = await _options.CreateMultiplexer(_options).ConfigureAwait(false);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added targeted tests in RedisMultiplexerOwnershipTests covering both a shared (IsShared: true, must NOT be disposed) and an exclusively-created (IsShared: false, must be disposed) IConnectionMultiplexer for all four non-streaming providers (grain storage, membership, reminders, grain directory), exercising both synchronous Dispose() and asynchronous DisposeAsync().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants