Sonarcloud not detecting deadlock with SemaphoreSlim

For one of our csharp projects one of our developers accidentally created a deadlock in a class using a SemaphoreSlim object where the SemaphoreSlim was not released in all execution paths.

Below is a stripped version of the class including the deadlock issue on line 34:

public abstract class BaseTokenPolicy
{
    private readonly SemaphoreSlim _semaphore = new(1);
    private readonly IMemoryCache _cache;

    protected BaseTokenPolicy(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

    public async Task ProcessAsync(string scope)
    {
        var token = await GetTokenAsync(scope);
    }

    public abstract Task<AccessToken> GetAccessTokenAsync(string scope);

    private async Task<AccessToken> GetTokenAsync(string scope)
    {
        if (GetValidToken(scope, out var cachedToken))
        {
            return cachedToken;
        }

        await _semaphore.WaitAsync();

        if (GetValidToken(scope, out var recentlyCachedToken))
        {
            return recentlyCachedToken;
        }

        try
        {
            var newToken = await GetAccessTokenAsync(scope);
            var cacheEntryOptions = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(newToken.Expiry - DateTimeOffset.UtcNow);

            _cache.Set(scope, newToken, cacheEntryOptions);

            return newToken;
        }
        finally
        {
            _semaphore.Release();
        }
    }

    private bool GetValidToken(string scope, [NotNullWhen(true)] out AccessToken token)
    {
        if (_cache.TryGetValue(scope, out AccessToken accessToken) && accessToken.Expiry > DateTimeOffset.UtcNow)
        {
            token = accessToken;
            return true;
        }
        else
        {
            token = new AccessToken("", DateTimeOffset.MinValue);
            return false;
        }
    }

    public class AccessToken
    {
        public AccessToken(string token, DateTimeOffset expiry, string type = "Bearer")
        {
            Token = token ?? throw new ArgumentNullException(nameof(token));
            Expiry = expiry;
            Type = type;
        }

        public string Token { get; set; }
        public DateTimeOffset Expiry { get; set; }
        public string Type { get; set; }

        public override string ToString() => $"{Type} {Token}";
    }
}

Hi Dave,

Thanks for your feedback and welcome to our community!

Our S2222 rule does not support yet the SemaphoreSlim type.

At this moment it checks only Monitor, Mutex, ReaderWriterLock, ReaderWriterLockSlim and SpinLock from the System.Threading namespace.

Hi @deMD,

I think semaphores would be an excellent addition to S2222. We may face problems because a semaphore has a capacity while the other locking patterns don’t. I created FN S2222: Add support for Semaphore/SemaphoreSlim · Issue #6375 · SonarSource/sonar-dotnet · GitHub in our backlog, and you can track the work on the feature there.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.