0

I have an api end point for people to call the service, I only want the GetCacheToken running one at a time if the cache has expired.

Apparently I have 3 calls or more call getCacheToken at the same time via getAccessToken, and it processes synchronously(not sure why), when it await _client.SendAsync(requestData); it will refresh the refresh token and the old refresh token wont be valid anymore.

So the first call went thru, and second and third failed, because they all using the old token to pass it in the send request.

Is there a way to lock the process when the cache is expired? As Lock cannot use on await calls. Thanks

Thought it would be handled by async await but no luck

    [System.Web.Http.HttpGet]
    [System.Web.Http.Route("getAccessToken")]
    public async Task<string> getAccessToken()
    {
        return await cacheToken.GetCacheToken();
    }       


    private static readonly SemaphoreSlim _mutex = new SemaphoreSlim(1,1); 

     public async Task<string> GetCacheToken()
    {
        ObjectCache cache = MemoryCache.Default;
        var cacheToken = cache.Get("refreshToken", null);
        string refreshToken = cacheToken == null ? GetToken() : cacheToken.ToString();

        if (!cache.Contains("apiToken"))
        {
            //it prevent multiple threads call to update refreshtoken at the same time
            await _mutex.WaitAsync();
            try
            {
                //Log.Information("Access token " + cache.Get("apiToken", null));
                var isNull = cache.Get("refreshToken", null) == null ? "Yes " : "No ";
                var logtime = DateTime.Now.ToString("O");
                Log.Information("refresh token is null : " + isNull + DateTime.Now.ToString("O") + " " + logtime);
                Log.Information(
                    "refresh token : " + refreshToken + " " + DateTime.Now.ToString("O") + " " + logtime);

                var httpContent = new StringContent("", Encoding.UTF8, "application/x-www-form-urlencoded");
                var dict = new Dictionary<string, string>();
                dict.Add("grant_type", "refresh_token");
                dict.Add("refresh_token", refreshToken);
                var requestData = new HttpRequestMessage
                {
                    Method = HttpMethod.Post,
                    RequestUri = new Uri("https://oauth2.sky.blackbaud.com/token"),
                    Content = new FormUrlEncodedContent(dict)
                };

                requestData.Headers.Authorization = new AuthenticationHeaderValue("Basic", Settings.BasicAuth);
                var results = await _client.SendAsync(requestData);
                Log.Information("run time " + DateTime.Now);
                var resultResponse = results.Content.ReadAsStringAsync().Result;

                try
                {
                    results.EnsureSuccessStatusCode();
                    var result = _js.Deserialize<TokenModel>(resultResponse);
                    //token expires in one hour from blackbaud
                    var expiration = DateTimeOffset.Now.AddMinutes(55);
                    cache.Set("apiToken", result.access_token, expiration);
                    cache.Set("refreshToken", result.refresh_token, expiration);
                    await UpdateToken(result.access_token, result.refresh_token);
                    Log.Information("refresh token after update : " + cache.Get("refreshToken", null) +
                                    DateTime.Now.ToString("O"));
                }
                catch (Exception e)
                {
                    var exceptionMessage = $"ResultMessage : {resultResponse} Exception: {e}.";
                    Log.Exception(e, exceptionMessage);
                    throw;
                }
            }
            finally
            {
                _mutex.Release();
            }
        }

        return cache.Get("apiToken", null).ToString();
    }

here's the log

12 Jul 2019 12:00:10.847
 ResultMessage : {"error":"invalid_grant"} Exception: System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request). at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at RaisersEdge.Infrastructure.Cache.<GetCacheToken>d__3.MoveNext().Exception: HttpRequestException {"Message":"Response status code does not indicate success: 400 (Bad Request).","Data":[],…Stack: at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at RaisersEdge.Infrastructure.Cache.<GetCacheToken>d__3.MoveNext()Category: Exception
12 Jul 2019 12:00:10.841
 run time 12/07/2019 12:00:10 PM
12 Jul 2019 12:00:09.981
 refresh token : 03124379add64a90850961e6a2021d6e 2019-07-12T12:00:09.9813412+12:00 2019-07-12T12:00:09.9813412+12:00
12 Jul 2019 12:00:09.981
 refresh token is null : No 2019-07-12T12:00:09.9813412+12:00 2019-07-12T12:00:09.9813412+12:00
12 Jul 2019 12:00:09.980
 refresh token after update : f0b569cfa2254bbfbd78e9d84ddd66ae2019-07-12T12:00:09.9803523+12:00
12 Jul 2019 12:00:09.951
 run time 12/07/2019 12:00:09 PM
12 Jul 2019 12:00:08.970
 refresh token : 03124379add64a90850961e6a2021d6e 2019-07-12T12:00:08.9212759+12:00 2019-07-12T12:00:08.9192713+12:00
12 Jul 2019 12:00:08.942
 refresh token is null : Yes 2019-07-12T12:00:08.9192713+12:00 2019-07-12T12:00:08.9192713+12:00
Dev
  • 171
  • 2
  • 18
  • You will find the reason and possible solutions here https://stackoverflow.com/questions/7612602/why-cant-i-use-the-await-operator-within-the-body-of-a-lock-statement – TheGeneral Jul 10 '19 at 07:01

1 Answers1

1

An asynchronous equivalent of lock is SemaphoreSlim.

If your desired code looks like this:

private readonly object _mutex = new object();

...
lock (_mutex)
{
  ...
  var results = await _client.SendAsync(requestData);
  ...
}

then you can get the desired behavior using SemaphoreSlim as such:

private readonly SemaphoreSlim _mutex = new SemaphoreSlim();

...
await _mutex.WaitAsync();
try
{
  ...
  var results = await _client.SendAsync(requestData);
  ...
}
finally
{
  _mutex.Release();
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810