Home > database >  SemaphoreSlim not working as expected - allowing multiple threads to use the same code
SemaphoreSlim not working as expected - allowing multiple threads to use the same code

Time:12-20

I have an API middle layer that captures requests, generates a token, and then passes that request on to the intended API endpoint. Only one token should be created at a time, so each request needs to check if a valid token exists and use it, or create a new one. If the middle layer API gets multiple requests at the same time, it should still only create one token.

To do this I am using SemaphoreSlim:

Controller that takes in a request to check for the current version of something:

[ApiController]
[Route("[controller]")]
[AuthorizationKey]

public class Controller : ControllerBase
{
    private readonly ILogger<Controller> Logger;
    private readonly Services Services;

    public Controller(ILogger<Controller> logger, Services services)
    {
        Logger = logger;
        Services = services;
    }

    [HttpGet]
    [Route("version")]
    [ResponseCache(NoStore = true, Duration = 0, Location = ResponseCacheLocation.None)]
    public async Task<ActionResult> Version()
    {
        try
        {
            var result = await Services.GetVersionAsync();

            var response = result.Content.ReadAsStringAsync();

            if (result.StatusCode == HttpStatusCode.OK)
            {
                return Ok(response.Result);
            }
            else if (result.StatusCode == HttpStatusCode.Unauthorized)
            {
                return StatusCode(401, response.Result);
            }
            else
            {
                return StatusCode(500, response.Result);
            }
        }

        catch (Exception ex)
        {
            return StatusCode(500);
        }
    }
}

Here is the class in my services:

public class Services
{
    private readonly ILogger<Services> Logger;
    private readonly IConfiguration Configuration;
    private readonly IHttpClientFactory HttpClientFactory;
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public Services(ILogger<Services> logger, IConfiguration configuration, IHttpClientFactory httpClientFactory)
    {
        Logger = logger;
        Configuration = configuration;
        HttpClientFactory = httpClientFactory;
    }

    public async Task<HttpResponseMessage> GetVersionAsync()
    {
        var httpClient = await CreateHttpClient();

        var response = await httpClient.GetAsync(Configuration["VERSION_ENDPOINT"]);

        return response;
    }

    #region Helpers

    private async Task<HttpClient> CreateHttpClient()
    {
        var token = await GetAccessTokenAsync();

        var httpClient = HttpClientFactory.CreateClient();
        httpClient.BaseAddress = new Uri(Configuration["API_URI"]);
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        return httpClient;
    }

    private async Task<string> GetAccessTokenAsync()
    {
        await _semaphore.WaitAsync();
        try
        {
            //Checks for a token in a table and returns it if it is valid, or gets a new one if one does not exist
            /if no token, then it calls RequestAccessTokenAsync()
        }
        finally
        {
            _semaphore.Release();
        }
    }

    private async Task<TokenResponse> RequestAccessTokenAsync()
    {
        //Gets a new token from a service
    }

    #endregion
}

If I submit a multiple requests at the same time it is creating multiple tokens. It's not always a 1 to 1 with how many requests I submit. For example I have done 2 requests and it created 2 tokens, 3 requests and it created 2 tokens, and now I just did 10 requests and it created 7 tokens, but either way I only want 1 valid token to be created at a time.

CodePudding user response:

Switching to static for the following code resolved the issue. Thank you again to user "Deleted" for the help.

private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
  • Related