0

In the following code:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
        private async Task<bool> Refresh()
        {
            log.Info("Refreshing Token.");
            Debug.WriteLine("Refreshing Token.");
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(TOKEN_URL);
            request.Method = "POST";
            request.ContentType = "application/json; charset=UTF-8";
            request.Headers.Add("Authorization", "basic " + authCode);
            request.Accept = "application/json, text/javascript, */*; q=0.01";
            request.ContentLength = payload.Length;

            log.Debug(request.Headers["Authorization"]);
            Debug.WriteLine(request.Headers["Authorization"]);

            using (Stream writeStream = request.GetRequestStream())
            {
                await writeStream.WriteAsync(payload, 0, payload.Length);
            }

            lock (tokenLock)
            {
                Debug.WriteLine($"Write Lock enabled? {tokenLock.IsWriteLockHeld}");
                tokenLock.EnterWriteLock();
                Debug.WriteLine($"Write Lock enabled? {tokenLock.IsWriteLockHeld}");
            }
            try
            {
                string body;
                using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
                {
                    int numericStatusCode = (int)response.StatusCode;
                    Debug.WriteLine($"Response Code: {numericStatusCode}");
                    if (response.StatusCode != HttpStatusCode.OK)
                    {
                        log.Error($"!!!!! Request failed. Received HTTP {response.StatusCode}");
                        body = string.Empty;
                    }
                    else
                    {
                        string responseValue = string.Empty;
                        using (Stream responseStream = response.GetResponseStream())
                        {
                            if (responseStream != null)
                            {
                                using (StreamReader reader = new StreamReader(responseStream))
                                {
                                    responseValue = await reader.ReadToEndAsync();
                                }
                            }
                        }
                        body = responseValue;

                        Debug.WriteLine($"Response Body = {body}");
                        log.Trace($"Response Body = {body}");
                    }
                }
                Debug.WriteLine($"Write Lock enabled? {tokenLock.IsWriteLockHeld}");
                if (!string.IsNullOrEmpty(body))
                {
                    _token = JsonConvert.DeserializeObject<AuthTokenInfo>(body, serializerSettings);

                    refreshUri = _token.RefreshTokenServerUri;
                    payload = Encoding.GetEncoding("utf-8").GetBytes(
                        JsonConvert.SerializeObject(new { grant_type = "refresh_token", _token.RefreshToken })
                    );
                    Debug.WriteLine($"Write Lock enabled? {tokenLock.IsWriteLockHeld}");
                    Debug.WriteLine($"Token Refreshed, Expires In = {_token.ExpiresIn}");
                    Debug.WriteLine($"Access Token = {_token.AccessToken}");

                    Debug.WriteLine($"New Token Refresh URI: {refreshUri}");
                    Debug.WriteLine($"New Refresh Token: {_token.RefreshToken}");
                    if (_token != null)
                    {
                        int refreshTime = 60 * 1000; // (Token.ExpiresIn.Value - (15 * 60)) * 1000;
                        log.Info($"Refreshing token in {refreshTime} milliseconds.");
                        Debug.WriteLine($"Refreshing token in {refreshTime} milliseconds.");

                        Task.Delay(refreshTime).ContinueWith(async (action) =>
                        {
                            log.Info("Refreshing token NOW.");
                            Debug.WriteLine("Refreshing token NOW.");
                            await Refresh();
                        });

                        Debug.WriteLine("Refresh scheduled.");
                    }
                }
            }
            finally
            {
                lock(tokenLock)
                {
                    Debug.WriteLine($"Write Lock enabled? {tokenLock.IsWriteLockHeld}");
                    tokenLock.ExitWriteLock();
                    Debug.WriteLine($"Write Lock enabled? {tokenLock.IsWriteLockHeld}");
                }
            }
            return true;
        }

When I execute this code, my debug output shows:

Refreshing Token.
Write Lock enabled? False
Write Lock enabled? True
Response Code: 200
Write Lock enabled? False
Write Lock enabled? False
Token Refreshed, Expires In = 3600
Refreshing token in 60000 milliseconds.
Refresh scheduled.
Write Lock enabled? False
Exception thrown: 'System.Threading.SynchronizationLockException' in System.Core.dll
Exception thrown: 'System.AggregateException' in mscorlib.dll
Exception thrown: 'System.TypeInitializationException' in InContactApi.dll
Exception thrown: 'System.TypeInitializationException' in mscorlib.dll
Exception thrown: 'System.AggregateException' in mscorlib.dll
An unhandled exception of type 'System.AggregateException' occurred in mscorlib.dll
One or more errors occurred.


Unhandled Exception: System.AggregateException: One or more errors occurred. ---> System.TypeInitializationException: The type initializer for 'InContact.Auth' threw an exception. ---> System.AggregateException: One or more errors occurred. ---> System.Threading.SynchronizationLockException: The write lock is being released without being held.
   at System.Threading.ReaderWriterLockSlim.ExitWriteLock()
   at InContact.AuthToken.<Refresh>d__12.MoveNext() in C:\Users\chill\source\repos\interactive_intelligence\InContactApi\InContactApi\Auth.cs:line 206
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at InContact.AuthToken..ctor() in C:\Users\chill\source\repos\interactive_intelligence\InContactApi\InContactApi\Auth.cs:line 106
   at InContact.Auth..cctor() in C:\Users\chill\source\repos\interactive_intelligence\InContactApi\InContactApi\Auth.cs:line 236
   --- End of inner exception stack trace ---
   at InContact.Auth.get_BaseURL()
   at InContact.InContactApi.MakeRequestURL(String subURL, Dictionary`2 query, String callerName) in C:\Users\chill\source\repos\interactive_intelligence\InContactApi\InContactApi\InContactApi.cs:line 127
   at InContact.InContactApi.<GetFolderListing>d__26.MoveNext() in C:\Users\chill\source\repos\interactive_intelligence\InContactApi\InContactApi\InContactApi.cs:line 607
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at CallLogger.ICRecordings.<DirTraverse>d__8.MoveNext() in C:\Users\chill\source\repos\interactive_intelligence\CallLogger\CallLogger\ICRecordings.cs:line 73
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at System.Threading.Tasks.Task`1.get_Result()
   at CallLogger.Program.Main() in C:\Users\chill\source\repos\interactive_intelligence\CallLogger\CallLogger\Program.cs:line 32
The program '[34036] PhoneLogger.exe' has exited with code 0 (0x0).

I am not understanding how my write lock is unlocking partway through the code, with the only write unlock line I have being at the end in a finally block.

Can anyone shed some light on this for me, and/or suggest a better approach?

I am dealing with an OAuth system that has an access token that must be refreshed every hour (when I am finally done with it). I have implemented this Refresh method to accomplish the goal, and use a Task.Delay().ContinueWith() call to schedule the refresh to run automatically. I am using a ReadWriterLockSlim so that I can lock the reads from continuing while the refresh is happening. Otherwise I want them to work normally. I need them locked because once I request the new token from the server on the refresh, I can no longer use the old auth token.

Cliff Hill
  • 121
  • 2
  • 8

1 Answers1

0

Aleks Andreev, thank you.

So the solution is to not use ReadWriterLockSlim, but rather to install the Nito.AsyncEx NuGet module and then use the AsyncReadWriterLock from it. Because ReadWriterLockSlim does not work with async/await.

Cliff Hill
  • 121
  • 2
  • 8