Skip to content

feat: enhance distributed lock service with Redis integration and con… - #15

Merged
Phelecks merged 1 commit into
mainfrom
feature/upgrade-di
Jan 21, 2026
Merged

Phelecks merged 1 commit into
mainfrom
feature/upgrade-di

Conversation

@Phelecks

Copy link
Copy Markdown
Member

…figuration updates

@Phelecks Phelecks self-assigned this Jan 21, 2026
Copilot AI review requested due to automatic review settings January 21, 2026 16:08

Copilot AI left a comment

Copy link
Copy Markdown

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 refactors the DistributedLockManager library to improve dependency injection patterns and resource management. The main enhancement is the introduction of IDistributedLockFactory as a central factory registered in the DI container, replacing the previous pattern where each service instance created its own RedLockFactory.

Changes:

  • Centralized RedLockFactory creation in Configuration.cs with singleton registration and logger support
  • Refactored DistributedLockService classes to use constructor injection with IDistributedLockFactory
  • Fixed critical resource management bug in integration tests where ConnectionMultiplexer was incorrectly disposed immediately after creation

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/DistributedLockManager/Configuration.cs Added singleton IDistributedLockFactory registration with RedLockFactory implementation, integrated ILoggerFactory support
src/DistributedLockManager/Services/DistributedLockService.cs Refactored both service classes to use primary constructors with injected IDistributedLockFactory instead of creating RedLockFactory instances
tests/DistributedLockManager.Tests/DistributedLockServiceIntegrationTests.cs Fixed ConnectionMultiplexer disposal issue, added wait strategy, logging support, and proper resource cleanup in teardown
Comments suppressed due to low confidence (1)

src/DistributedLockManager/Services/DistributedLockService.cs:16

  • The void version of RunWithLockAsync doesn't throw an exception when the lock is not acquired, creating an inconsistent behavior with the generic version (IDistributedLockService). When the lock fails to be acquired, this method silently completes without executing the function, which could lead to data inconsistency or missed operations. Consider throwing an InvalidOperationException similar to the generic version to ensure consistent error handling across both service implementations.
    public async Task RunWithLockAsync(Func<Task> func, string key, CancellationToken cancellationToken, int expiryInSecond = 30, int waitInSecond = 10, int retryInSecond = 1)
    {
        // blocks until acquired or 'wait' timeout
        await using var redLock = await distributedLockFactory.CreateLockAsync(key, TimeSpan.FromSeconds(expiryInSecond), TimeSpan.FromSeconds(waitInSecond),
            TimeSpan.FromSeconds(retryInSecond), cancellationToken);

        if (redLock.IsAcquired)
            await func();
    }

Comment on lines 18 to 26
public async Task RunWithLockAsync(Task task, string key, CancellationToken cancellationToken, int expiryInSecond = 30, int waitInSecond = 10, int retryInSecond = 1)
{
// blocks until acquired or 'wait' timeout
await using var redLock = await _redLockFactory.CreateLockAsync(key, TimeSpan.FromSeconds(expiryInSecond), TimeSpan.FromSeconds(waitInSecond),
await using var redLock = await distributedLockFactory.CreateLockAsync(key, TimeSpan.FromSeconds(expiryInSecond), TimeSpan.FromSeconds(waitInSecond),
TimeSpan.FromSeconds(retryInSecond), cancellationToken);

if (redLock.IsAcquired)
await task;
}

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

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

The void version of RunWithLockAsync doesn't throw an exception when the lock is not acquired, creating an inconsistent behavior with the generic version (IDistributedLockService). When the lock fails to be acquired, this method silently completes without executing the task, which could lead to data inconsistency or missed operations. Consider throwing an InvalidOperationException similar to the generic version to ensure consistent error handling across both service implementations.

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +63
scope?.Dispose();
_connectionMultiplexer?.Dispose();

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

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

The service provider should be disposed before disposing the ConnectionMultiplexer to ensure proper cleanup order. The scope's service provider may still hold references to services that depend on the ConnectionMultiplexer. Consider storing the ServiceProvider and disposing it explicitly before disposing the ConnectionMultiplexer to prevent potential resource cleanup issues.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +22
services.AddSingleton<IDistributedLockFactory, RedLockFactory>(sp =>
{
var connectionMultiplexer = sp.GetRequiredService<IConnectionMultiplexer>();
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();

return RedLockFactory.Create([new RedLockMultiplexer(connectionMultiplexer)], loggerFactory);
});

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

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

The factory registration requires ILoggerFactory but this dependency isn't documented. If ILoggerFactory is not registered in the DI container when AddDistributedLockManager is called, GetRequiredService will throw an exception. Consider either documenting this prerequisite in XML comments or making the logger optional by using GetService and providing a null logger to the factory if logging isn't configured.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +40
// Wait a bit for Redis to be fully ready
await Task.Delay(1000);

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

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

The hardcoded 1-second delay after container start is a brittle approach to waiting for Redis readiness. The test already uses WithWaitStrategy on line 31 which should handle this. If the wait strategy isn't sufficient, consider increasing its timeout or using a more reliable readiness check (e.g., attempting a Redis PING command with retry logic) instead of an arbitrary delay.

Suggested change
// Wait a bit for Redis to be fully ready
await Task.Delay(1000);

Copilot uses AI. Check for mistakes.
@Phelecks
Phelecks merged commit 8592479 into main Jan 21, 2026
8 checks passed
@Phelecks
Phelecks deleted the feature/upgrade-di branch January 21, 2026 16:44
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