Skip to content

v1.0.24#64

Merged
yilmaztayfun merged 2 commits into
release-v1.0from
master
May 18, 2026
Merged

v1.0.24#64
yilmaztayfun merged 2 commits into
release-v1.0from
master

Conversation

@yilmaztayfun

@yilmaztayfun yilmaztayfun commented May 18, 2026

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Introduce handle-based distributed lock lifecycle management with TTL extension support and per-acquisition owners, and align docs, telemetry, and tests with the new API.

New Features:

  • Add IDistributedLockHandle abstraction for scope-based distributed locks with TTL extension and explicit release.
  • Implement Redis and Dapr distributed lock handle implementations and expose them via TryAcquireLockAsync.

Bug Fixes:

  • Mark legacy ReleaseLockAsync methods as obsolete to discourage unsafe static-owner usage and guide callers to handle-based APIs.

Enhancements:

  • Change ExecuteWithLockAsync to return an acquisition/result tuple and update usage docs accordingly.
  • Generate unique lock owner identifiers per acquisition for Redis and Dapr providers to improve safety of concurrent locks.
  • Extend distributed lock telemetry with span and tagging for TTL extension operations.
  • Clarify documentation around short vs long operations, handle-based APIs, and long-running pipeline patterns.
  • Simplify Entity Framework Core tracing configuration by relying on default options.

Tests:

  • Add unit tests for Redis and Dapr distributed lock services and their handle implementations to validate acquisition, ownership, extension, and release behavior.

yilmaztayfun and others added 2 commits May 18, 2026 11:30
…ased lock lifecycle

Replace IAsyncDisposable return with IDistributedLockHandle on
TryAcquireLockAsync, enabling TTL extension (Redis) and explicit
release. Each acquisition now generates a unique owner
(MachineName:Guid) instead of a static client identifier, fixing
concurrent lock safety.

- Add IDistributedLockHandle interface (ExtendAsync, ReleaseAsync)
- Add RedisDistributedLockHandle with atomic Lua-based extend/release
- Add DaprDistributedLockHandle (extend unsupported, logs warning)
- Mark ReleaseLockAsync as [Obsolete] on both providers
- Delete RedisLockHandle, replaced by RedisDistributedLockHandle
- Add BBT.Aether.Infrastructure.Tests with unit tests for handles and services
- Update distributed-lock docs with handle API and TTL extension patterns
- Upgrade Dapr 1.16.1 → 1.17.9, OpenTelemetry 1.14.0 → 1.15.x,
  Npgsql.OpenTelemetry 8.0.6 → 10.0.2
- Remove deprecated EF Core OTel instruion options (SetDbStatementForText)

BREAKING CHANGE: TryAcquireLockAsync returns IDistributedLockHandle?
instead of IAsyncDisposable?. ReleaseLockAsync is obsolete — use the handle.
…try-tracing-spans-to-distributed-event-bus-pipeline

feat(distributed-lock)!: introduce IDistributedLockHandle for scope-b…
@yilmaztayfun yilmaztayfun self-assigned this May 18, 2026
@yilmaztayfun yilmaztayfun requested review from a team May 18, 2026 08:30
@sourcery-ai

sourcery-ai Bot commented May 18, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a new scope-based distributed lock handle abstraction with TTL extension for Redis, adjusts the distributed lock service interfaces and implementations (Redis and Dapr) to return handles with unique owners, deprecates legacy release methods, updates documentation and examples for handle-based usage vs ExecuteWithLock patterns, adds telemetry for lock extension, simplifies EF Core tracing configuration, and covers the new behavior with unit tests.

Sequence diagram for scope-based Redis lock with TTL extension

sequenceDiagram
    actor Service
    participant LockService as IDistributedLockService
    participant RedisService as RedisDistributedLockService
    participant Handle as RedisDistributedLockHandle
    participant Redis as RedisDatabase

    Service->>LockService: TryAcquireLockAsync("pipeline:123", 60, ct)
    LockService->>RedisService: TryAcquireLockAsync("pipeline:123", 60, ct)
    RedisService->>Redis: StringSetAsync(lockKey, owner, NX, expiry=60s)
    Redis-->>RedisService: acquired = true
    RedisService-->>LockService: Handle
    LockService-->>Service: Handle

    Service->>Handle: ExtendAsync(120, ct)
    Handle->>Redis: ScriptEvaluateAsync(ExtendScript, lockKey, owner, 120s)
    Redis-->>Handle: extended = 1
    Handle-->>Service: true

    Service->>Handle: DisposeAsync()
    Handle->>Redis: ScriptEvaluateAsync(ReleaseScript, lockKey, owner)
    Redis-->>Handle: released > 0
    Handle-->>Service: completed
Loading

File-Level Changes

Change Details Files
Introduce IDistributedLockHandle abstraction and update IDistributedLockService to return lock handles instead of booleans, including a new ExecuteWithLockAsync result tuple.
  • Add IDistributedLockHandle interface with LockKey, Owner, ExtendAsync, and ReleaseAsync members to represent an acquired lock scope with async disposal semantics.
  • Change IDistributedLockService.TryAcquireLockAsync to return IDistributedLockHandle? and document handle-based lifecycle management.
  • Mark IDistributedLockService.ReleaseLockAsync as obsolete and update XML docs to steer consumers toward handle.ReleaseAsync/DisposeAsync.
  • Adjust ExecuteWithLockAsync to return a (bool Acquired, T? Result) tuple and update calling examples to pattern-match the result.
framework/src/BBT.Aether.Core/BBT/Aether/DistributedLock/IDistributedLockHandle.cs
framework/src/BBT.Aether.Core/BBT/Aether/DistributedLock/IDistributedLockService.cs
framework/docs/distributed-lock/README.md
Implement provider-specific lock handle types for Redis and Dapr with proper owner scoping, TTL management, and telemetry, and wire them into the distributed lock services.
  • Add RedisDistributedLockHandle implementing IDistributedLockHandle and IDisposable, using Lua scripts to atomically extend and release locks, emitting DistributedLock.Extend and DistributedLock.Release telemetry spans, and guarding against double-dispose/release.
  • Replace RedisDistributedLockService.TryAcquireLockAsync to create RedisDistributedLockHandle instances when a Redis StringSet-based lock is acquired, and mark ReleaseLockAsync obsolete while fixing script argument types and adding a GenerateUniqueOwner helper.
  • Add DaprDistributedLockHandle implementing IDistributedLockHandle with explicit release via DaprClient.Unlock, a no-op-but-logged ExtendAsync (returns false), and telemetry integration, using an internal disposed flag.
  • Update DaprDistributedLockService.TryAcquireLockAsync to generate a unique owner per acquisition, wrap successful locks in DaprDistributedLockHandle instead of relying on the Dapr resource lock object, and add a GenerateUniqueOwner helper while marking ReleaseLockAsync obsolete.
framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockHandle.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockService.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockHandle.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs
Enhance distributed lock documentation with handle-based, scope-oriented patterns, TTL extension guidance, and updated telemetry semantics.
  • Update the README overview to call out scope-based lock lifecycle management and clarify that ExecuteWithLock is recommended for short operations.
  • Refactor usage examples to show ExecuteWithLockAsync returning (acquired, result) and handle-based patterns using await using, ExtendAsync, and explicit ReleaseAsync.
  • Add examples for long-running pipelines and migrations using TTL extension, plus guidance on when to prefer the handle API vs ExecuteWithLock.
  • Extend telemetry docs to include DistributedLock.Extend span, lock.extended tag, and updated example trace hierarchy emphasizing acquire/extend/release events, and add best practices for handle usage and TTL management.
framework/docs/distributed-lock/README.md
Add unit tests for the new lock handle implementations and distributed lock services to validate behavior, unique owner generation, and safe disposal semantics.
  • Create tests for RedisDistributedLockHandle covering property exposure, ExtendAsync success/failure/after-dispose/exception behavior, ReleaseAsync idempotency, and Dispose/DisposeAsync releasing exactly once.
  • Create tests for DaprDistributedLockHandle verifying properties, ExtendAsync always returns false and does not call Dapr.Lock, and Release/Dispose idempotency with Unlock calls.
  • Add tests for RedisDistributedLockService to verify successful/failed lock acquisition, unique owner IDs per acquisition, and exception handling returning null handles.
  • Add tests for DaprDistributedLockService to verify successful/failed/null/exceptional Lock responses and unique owner generation, leveraging TryLockResponse stubs.
  • Introduce a new infrastructure test project to host these tests and wire it into the solution and project references.
framework/test/BBT.Aether.Infrastructure.Tests/BBT.Aether.Infrastructure.Tests.csproj
framework/test/BBT.Aether.Infrastructure.Tests/BBT/Aether/DistributedLock/Redis/RedisDistributedLockHandleTests.cs
framework/test/BBT.Aether.Infrastructure.Tests/BBT/Aether/DistributedLock/Redis/RedisDistributedLockServiceTests.cs
framework/test/BBT.Aether.Infrastructure.Tests/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockHandleTests.cs
framework/test/BBT.Aether.Infrastructure.Tests/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockServiceTests.cs
framework/BBT.Aether.slnx
Adjust telemetry configuration and dependencies to align with current OpenTelemetry EF Core instrumentation defaults.
  • Simplify EF Core tracing configuration in AddAetherTelemetry by using AddEntityFrameworkCoreInstrumentation() without custom statement flags, delegating configuration to library defaults.
  • Update central package props and infrastructure project references as needed to support the new tests and instrumentation changes.
framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs
Directory.Packages.props
framework/src/BBT.Aether.Infrastructure/BBT.Aether.Infrastructure.csproj

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9d65a082-a0c0-475b-8e3d-96ac995a3219

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch master

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@yilmaztayfun yilmaztayfun merged commit 353745e into release-v1.0 May 18, 2026
4 of 6 checks passed

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces a scope-based distributed lock API through the new IDistributedLockHandle interface, enabling explicit lock release and TTL extension (implemented for Redis). The changes include updating Dapr and OpenTelemetry dependencies, adding infrastructure unit tests, and updating documentation. Feedback highlights critical logic errors in the deprecated ReleaseLockAsync methods where random owner IDs prevent successful unlocking. Other recommendations include restoring EF Core SQL capture settings to maintain observability, addressing a sync-over-async anti-pattern in the Redis handle's disposal logic, and removing an unused constructor parameter.

Comment on lines +69 to 70
var lockOwner = GenerateUniqueOwner();
await daprClient.Unlock(storeName, resourceId, lockOwner, 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 ReleaseLockAsync method is now logically broken. It generates a new unique owner (containing a random GUID) for the Unlock call. Since Dapr requires the owner ID to match the one used during acquisition, this call will always fail to release the lock. If this method is intended to be deprecated and non-functional, it should be documented as such or return false immediately to avoid a useless network call. Otherwise, it should use a stable identifier.

{
var database = redisConnection.GetDatabase();
var lockOwner = GetClientIdentifier();
var lockOwner = GenerateUniqueOwner();

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

Similar to the Dapr implementation, ReleaseLockAsync is now broken because it generates a random unique owner for the release script. The owner must match the one that acquired the lock for the Lua script to successfully delete the key. This method will now always fail to release locks.

efOptions.SetDbStatementForText = true;
efOptions.SetDbStatementForStoredProcedure = true;
});
tracing.AddEntityFrameworkCoreInstrumentation();

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

Removing the explicit configuration for Entity Framework Core instrumentation will result in SQL statements no longer being captured in traces. By default, SetDbStatementForText and SetDbStatementForStoredProcedure are false in the OpenTelemetry instrumentation to prevent accidental exposure of sensitive data. If capturing the SQL queries is still desired for observability, these options should be restored.

                    tracing.AddEntityFrameworkCoreInstrumentation(efOptions =>
                    {
                        efOptions.SetDbStatementForText = true;
                        efOptions.SetDbStatementForStoredProcedure = true;
                    });

string lockKey,
string owner,
ILogger logger)
: IDistributedLockHandle, IDisposable

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

The RedisDistributedLockHandle implements IDisposable in addition to IAsyncDisposable, but the synchronous Dispose implementation (line 141) uses GetAwaiter().GetResult(). This is a sync-over-async anti-pattern that can lead to deadlocks or thread pool starvation. Since the IDistributedLockHandle interface only requires IAsyncDisposable, it is recommended to remove the IDisposable implementation and the synchronous Dispose method.

    : IDistributedLockHandle

@@ -17,15 +17,15 @@ public class RedisDistributedLockService(
IApplicationInfoAccessor applicationInfoAccessor)

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

The applicationInfoAccessor parameter is no longer used in this class. It should be removed from the constructor.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 102 complexity · 51 duplication

Metric Results
Complexity 102
Duplication 51

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@sonarqubecloud

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot 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.

Hey - I've left some high level feedback:

  • The obsolete ReleaseLockAsync implementations for both Dapr and Redis now generate a new owner per call (GenerateUniqueOwner), which means they can no longer successfully release locks acquired earlier; if you need to keep them usable for legacy callers, consider reusing the original owner or clearly documenting that they will effectively never release a lock.
  • The README examples and interface snippet show ExecuteWithLockAsync<T> returning a (bool Acquired, T? Result) tuple, but the core IDistributedLockService interface still returns Task<T?>; aligning the public API or adjusting the samples would avoid confusion and compilation issues for consumers copying the examples.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The obsolete `ReleaseLockAsync` implementations for both Dapr and Redis now generate a new owner per call (`GenerateUniqueOwner`), which means they can no longer successfully release locks acquired earlier; if you need to keep them usable for legacy callers, consider reusing the original owner or clearly documenting that they will effectively never release a lock.
- The README examples and interface snippet show `ExecuteWithLockAsync<T>` returning a `(bool Acquired, T? Result)` tuple, but the core `IDistributedLockService` interface still returns `Task<T?>`; aligning the public API or adjusting the samples would avoid confusion and compilation issues for consumers copying the examples.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

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.

1 participant