AdrianJSClark / aydsko-iracingdata

A .NET access library for querying the iRacing "Data" API.
MIT License
29 stars 8 forks source link

Unauthorized Responses Do Not Retry And Calls Continue To Fail #231

Open AdrianJSClark opened 1 week ago

AdrianJSClark commented 1 week ago

Report from a user:

I'm observing some strange behavior where after a certain amount of time has elapsed, requests to iRacing begin to fail. I'm probably not handling the response erroring in an ideal way, but I was expecting the library to recognize a 401 and to request a new token using the credentials configured and retry (which should be successful). Do you have any ideas why I might be seeing this?

Aydsko.iRacingData.Exceptions.iRacingUnauthorizedResponseException:
   at Aydsko.iRacingData.DataClient.HandleUnsuccessfulResponse (Aydsko.iRacingData, Version=2403.0.0.0, Culture=neutral, PublicKeyToken=null: /_/src/Aydsko.iRacingData/DataClient.cs:2145)

Is this singleton related in some way? Is there a better way to handle this cookie storage?

https://github.com/AdrianJSClark/aydsko-iracingdata/blob/a1187fa49796efb6fddf9b8831c520db10b4f5be/src/Aydsko.iRacingData/ServicesExtensions.cs#L113C1-L113C57

DuncWatts commented 6 days ago

I have an extension method that resets my cookie container whenever I receive an iRacingUnauthorizedResponseException, could that be integrated into the library directly?

        public static async Task<T> UnwrapAsync<T>(this Task<DataResponse<T>> responseTask)
        {
            try
            {
                var response = await responseTask;
                return response.Data;
            }
            catch (iRacingUnauthorizedResponseException)
            {
                ResetCookies();
                throw;
            }
        }
AdrianJSClark commented 6 days ago

I might need something like that.

AdrianJSClark commented 6 days ago

Idea: Move the "Is logged in" check inside the "Create response" method. Catch the unauthorised exception the first time and retry. If that doesn't work, the exception can be raised.

AdrianJSClark commented 6 days ago

I have an extension method that resets my cookie container whenever I receive an iRacingUnauthorizedResponseException, could that be integrated into the library directly?


        public static async Task<T> UnwrapAsync<T>(this Task<DataResponse<T>> responseTask)

        {

            try

            {

                var response = await responseTask;

                return response.Data;

            }

            catch (iRacingUnauthorizedResponseException)

            {

                ResetCookies();

                throw;

            }

        }

Thanks for the code too. Much appreciated. 👍

DuncWatts commented 6 days ago

Note that extension method doesn't respect the rate limit, I use it only in specific scenarios where I know I'm not going to hit the rate limit. Most of my stuff is hosted in an Azure Function where you're charged for how long the codes run for, so when it fails I want it to fail fast and not sit in a retry loop.

I have a different extension method when I'm pulling in larger amounts of data which does respect the rate limit and supports parallelism.

        public static DateTimeOffset? NextReset { get; set; }
        public static async Task<T> AutoRetryAsync<T>(this IDataClient client, int parallelism, Expression<Func<IDataClient, Task<DataResponse<T>>>> expression)
        {
            int tries = 0;

            while (true)
            {
                try
                {
                    var result = await expression.Compile().Invoke(client);

                    NextReset = result.RateLimitReset;

                    if (result.RateLimitRemaining < parallelism)
                    {
                        TimeSpan timeToWait = NextReset.GetValueOrDefault(DateTimeOffset.UtcNow) - DateTimeOffset.UtcNow;
                        if (timeToWait > TimeSpan.Zero)
                        {
                            await Task.Delay(timeToWait.Add(TimeSpan.FromSeconds(1)));
                        }
                    }
                    return result.Data;
                }
                catch (iRacingRateLimitExceededException)
                {
                    TimeSpan timeToWait = NextReset.GetValueOrDefault(DateTimeOffset.UtcNow) - DateTimeOffset.UtcNow;
                    if (timeToWait > TimeSpan.Zero)
                    {
                        await Task.Delay(timeToWait.Add(TimeSpan.FromSeconds(1)));
                    }
                }
                catch (iRacingUnauthorizedResponseException)
                {
                    ResetCookies();

                    tries++;
                    if (tries > 5)
                    {
                        throw;
                    }
                    await Task.Delay(5000);
                }
                catch
                {
                    tries++;

                    if (tries > 5)
                    {
                        throw;
                    }
                    await Task.Delay(5000);
                }
            }
        }
AdrianJSClark commented 4 days ago

I've published a pre-release which includes a retry for unauthorized requests. Hoping to make it a final release soon.

That is release 2404.5.0-beta available on NuGet here: https://www.nuget.org/packages/Aydsko.iRacingData/2404.5.0-beta