The App i am building needs to call some Api's, running in GKE, to retrieve some values. In this scenario it is default values for settings.
The Api in GKE is currently in a crash loop, due to the needed database not being setup yet. In the case that the Api is down, like now, the app should simply timeout on the request, which it from my code should handle just fine.
Problem is, it never times out, even when the exact same request done in Postman times out after ~10 sec. The constructor for my class, that does the calls to the Api can be seen below. Disregard the IHandler, seen below, that is for my Entity Framework Core. It is inside the method 'ApiGet' that the issue is. The 'ApiGet' method is called from my Get method, which i am not gonna show, as that is a monster of a method (112 lines...)
public abstract class ControllerProxy
{
protected RestClient Client { get; }
protected IHandler Handler { get; }
protected ControllerProxy(IHandler handler, string baseAddress, string controller)
{
Client = new RestClient(new Uri($"{baseAddress}api/{controller}/"));
Handler = handler;
}
}
public class SettingsControllerProxy : ControllerProxy, ISettingsController<SettingProxy>
{
public SettingsControllerProxy(IHandler handler, string baseAddress, string controller) : base(handler, baseAddress, controller)
{
Client.ThrowOnAnyError = true;
Client.Timeout = 1000;
}
private async Task<IEnumerable<SettingProxy>> ApiGet(SettingProxy entity)
{
// Creates request for the settings matching the ValueType and key
var request = new RestRequest() {Timeout = 1000, ReadWriteTimeout = 1000, Method = Method.GET }.AddQueryParametersFromObject(entity,
new List<string>() {nameof(entity.ValueType), nameof(entity.Key)});
try
{
var response = await Client.ExecuteTask(request, TimeSpan.FromSeconds(10));
// Returns the Deserialized list of Settings if it is successful, otherwise gives an empty list. Check for empty list on the return side.
return response.IsSuccessful ? JsonConvert.DeserializeObject<List<SettingProxy>>(response.Content) : new List<SettingProxy>();
}
catch (Exception e)
{
Logging.Log(LogType.Warning, $"Caught a {e.GetType().Name} in {nameof(ApiGet)}; " +
$"Exception => {e}; returning an empty list to caller...");
return new List<SettingProxy>();
}
}
...
}
The method 'AddQueryParametersFromObject' can be seen below for reference, followed by 'ExecuteTask'. When i make my call to ExecuteAsync inside ExecuteTask, it stalls and never gets any further.
public static IRestRequest AddQueryParametersFromObject<T>(this IRestRequest request, T input,
IEnumerable<string> propertyNames) where T : class
{
var properties = typeof(T).GetProperties().Where(x => propertyNames.Any(z => x.Name == z)).ToList();
return properties.Aggregate(request, (current, info) => current.AddQueryParameter(info.Name, info.GetValue(input).ToString()));
}
public static async Task<IRestResponse> ExecuteTask(this IRestClient client, IRestRequest request, TimeSpan taskTimeout = default)
{
var TaskCompletionSource = new TaskCompletionSource<IRestResponse>();
var source = new CancellationTokenSource();
if (taskTimeout != default)
source.CancelAfter(taskTimeout);
try
{
var response = await client.ExecuteAsync(request, source.Token);
if (response.ResponseStatus == ResponseStatus.Error)
TaskCompletionSource.SetException(response.ErrorException);
else
TaskCompletionSource.SetResult(response);
}
catch (Exception e)
{
TaskCompletionSource.SetException(e);
}
return await TaskCompletionSource.Task;
}
Does RestSharp simply not work for Xamarin? I would like to continue using RestSharp, as it seems alot easier to create request with it, over a strait up HttpClient.
I have looked at the 'FubarCoder.RestSharp.Portable' Nuget package, but as its quite some time since it was last updated, i am reluctant to use it.
Fell free to as for more details, if there is anything else i might be able to supply you with.
During my work, trying to solve this, i created some code to Timeout the request on the client side. During the creation of this code, it suddenly started to work, with the request timing out as it should.
I am unsure what change i made, that fixed it, so i am just gonna show my updated ApiGet and ExecuteTask methods.
private async Task<IEnumerable<SettingProxy>> ApiGet(SettingProxy entity)
{
// Creates request for the settings matching the ValueType and key
var request = new RestRequest() { Timeout = 2000, ReadWriteTimeout = 2000, Method = Method.GET }.AddQueryParametersFromObject(entity,
new List<string>() { nameof(entity.ValueType), nameof(entity.Key) });
try
{
var response = await Client.Client.ExecuteTask(request, TimeSpan.FromSeconds(6));
// Returns the Deserialized list of Settings if it is successful, otherwise gives an empty list. Check for empty list on the return side.
return response.IsSuccessful ? JsonConvert.DeserializeObject<List<SettingProxy>>(response.Content) : new List<SettingProxy>();
}
catch (Exception e)
{
Logging.Log(LogType.Warning, $"Caught a {e.GetType().Name} in {nameof(ApiGet)}, while attempting to get settings for key: {entity.Key}; " +
$"Exception => {e}; returning an empty list to caller...");
return new List<SettingProxy>();
}
}
public static async Task<IRestResponse> ExecuteTask(this IRestClient client, IRestRequest request, TimeSpan timeout = default)
{
var TaskCompletionSource = new TaskCompletionSource<IRestResponse>();
var source = new CancellationTokenSource();
if (timeout != default)
request.OnBeforeRequest += http =>
{
http.Timeout = (int) timeout.TotalMilliseconds;
};
Task<IRestResponse> run = null;
try
{
run = client.ExecuteAsync(request, source.Token);
if (timeout != default)
{
var tasks = new[] { run, Task.Delay(timeout, source.Token) };
var finished = Task.WaitAny(tasks);
if (!run.IsCompleted && finished == 1)
{
source.Cancel();
throw new TimeoutException($"Request timed out by Client Code, to prevent stall.");
}
}
var response = await run;
if (response.ResponseStatus != ResponseStatus.Completed)
TaskCompletionSource.SetException(response.ErrorException);
else
TaskCompletionSource.SetResult(response);
}
catch (Exception e)
{
TaskCompletionSource.SetException(e);
}
run?.Dispose();
return await TaskCompletionSource.Task;
}
Running my code, at the moment it is primarly the client code that times out the request, but ones in a while, it is a WebException, indicating a timeout, that is thrown.
I have a hunch, that the issue was caused by my Xamarin Forms not deploying the most resent changes, which my project has been plagued with resently. Toggleing "Use Fast Deployment" either on or off, under the Android project properties, does seem to "fix" thids issue for some unknown amount of time.