Skip to content

Redis semaphore: current count #250

Merged
madelson merged 4 commits into
madelson:release-2.7from
teesofttech:master
Aug 10, 2025
Merged

Redis semaphore: current count #250
madelson merged 4 commits into
madelson:release-2.7from
teesofttech:master

Conversation

@teesofttech

Copy link
Copy Markdown
Contributor

@teesofttech

Copy link
Copy Markdown
Contributor Author

@madelson can you review this PR for the semaphore count?

@madelson madelson left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This looks great @teesofttech thanks for the contribution.

I left a few comments with minor changes I'd like to see before merging it in. If you have a chance to go through those leave a comment and @mention me to take the next steps.

public int GetCurrentCount()
{
return GetCurrentCountAsync().GetAwaiter().GetResult();
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Should be:

public int GetCurrentCount() => SyncViaAsync.Run(s => s.GetCurrentCountAsync(), state: this);

This avoids the sync over async pattern that GetResult() is, which is inefficient and can result in thread starvation.

/// </summary>
public string Name => this.Key.ToString();

public int GetCurrentCount()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

These are public methods; please add doc comments to each. In this case I think we can just say that this functionality is equivalent to SemaphoreSlim.CurrentCount


public async ValueTask<int> GetCurrentCountAsync()
{
var database = _databases[0];

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

this._databases, per the codebase style

/// <returns></returns>
internal async ValueTask<int> GetCurrentCountAsync(IDatabaseAsync database)
{
const string script = @"return redis.call('zcard', KEYS[1])";

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Using a script here adds unnecessary overhead since we're just executing a single command. Also, we want to support sync-via-async execution.

Let's just do database.SortedSetLengthAsync() with CommandFlags.DemandMaster.

To support sync-via-async execution, we should actually do:

var length = SyncViaAsync.IsSynchronous
    ? database.SortedSetLength(...)
    :  await database.SortedSetLengthAsync(...).ConfigureAwait(false);

/// </summary>
/// <param name="database"></param>
/// <returns></returns>
internal async ValueTask<int> GetCurrentCountAsync(IDatabaseAsync database)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

public

/// </summary>
/// <param name="database"></param>
/// <returns></returns>
internal async ValueTask<int> GetCurrentCountAsync(IDatabaseAsync database)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Let's call this GetAcquiredCountAsync since it doesn't return the remaining count.

/// Get Current Count
/// </summary>
/// <param name="database"></param>
/// <returns></returns>

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Let's remove this doc comment; it isn't adding anything

var database = _databases[0];
var primitive = new RedisSemaphorePrimitive(this.Key, this.MaxCount, this._options.RedLockTimeouts);
var acquiredCount = await primitive.GetCurrentCountAsync(database).ConfigureAwait(false);
return this.MaxCount - acquiredCount;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Let's wrap this with Math.Max(0, ...). Because semaphores can be declared with different counts on different machines (not a good thing, but it could happen), it's possible that acquiredCount could be greater than MaxCount. However, CurrentCount shouldn't return a negative number in that case.


expectedAvailable.ShouldEqual(available);
databaseMock.VerifyAll();
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Let's add one additional test case that demonstrates this working end-to-end.

See RedisDistributedLockTest.TestCanAcquireLockWhenCurrentCultureIsTurkishTurkey() as an example of how you can get a database inside the test case.

This end-to-end test would not have the "CI" tag.

@teesofttech

Copy link
Copy Markdown
Contributor Author

@madelson Have addressed those comments. Can you take a look at it?

@teesofttech
teesofttech requested a review from madelson August 4, 2025 22:02
Comment thread src/DistributedLock.Redis/PublicAPI.Unshipped.txt Outdated
@teesofttech
teesofttech requested a review from madelson August 4, 2025 23:25
@teesofttech

teesofttech commented Aug 4, 2025

Copy link
Copy Markdown
Contributor Author

@madelson the build still failed. Checking the logs history, seems it has been failing before

@teesofttech

Copy link
Copy Markdown
Contributor Author

@madelson the build still failed. Checking the log history, seems it has been failing before

@madelson
madelson changed the base branch from master to release-2.7 August 10, 2025 14:09
@madelson

Copy link
Copy Markdown
Owner

Checking the log history, seems it has been failing before

Ok; I'll take a look after merging.

@madelson
madelson merged commit 2e5b192 into madelson:release-2.7 Aug 10, 2025
1 check failed
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