System.ObjectDisposedException: The CancellationTokenSource has been disposed - c#

I sent 2 and more requests together for rest service and after second response I get an error. Also locally all works great, but on the env I catch exception.
The operation was canceled.
The error acquired: System.ObjectDisposedException: The
CancellationTokenSource has been disposed.
at
System.Threading.CancellationTokenSource.ThrowObjectDisposedException()
Can anyone suggest something?
[NotNull]
[ItemNotNull]
private async Task<TResponse> PostAsync<TRequest, TResponse>([NotNull] string path, [NotNull] TRequest request, CancellationToken cancellationToken) where TRequest : class where TResponse : class {
var logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.Console()
.CreateLogger();
Console.WriteLine("client");
var client = this.GetHttpClient();
Console.WriteLine("content");
using var content = this.CreateHttpContent(request);
Console.WriteLine("response");
try {
using var response = await client.PostAsync(path, content, cancellationToken).ConfigureAwait(false);
return await this.ParseResponseAsync<TResponse>(response, cancellationToken).ConfigureAwait(false);
} catch (OperationCanceledException exception) {
logger.Verbose(exception.Message);
throw;
} catch (Exception exception) {
Console.WriteLine(exception);
logger.Verbose(exception.Message);
logger.Error(exception.StackTrace);
logger.Fatal(exception.InnerException?.Message);
throw;
}
}
[NotNull]
private HttpClient GetHttpClient() {
var client = new HttpClient { BaseAddress = this.m_baseAddress };
var headers = client.DefaultRequestHeaders;
headers.Accept.Clear();
headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MEDIA_TYPE_JSON));
headers.ConnectionClose = true;
return client;
}
[NotNull]
private HttpContent CreateHttpContent<TRequest>([NotNull] TRequest request) where TRequest : class {
if(request == null) throw new ArgumentNullException(nameof(request));
var content = JsonSerializer.Serialize(request, s_serializerOptions);
return new StringContent(content, Encoding.UTF8, MEDIA_TYPE_JSON);
}
public async Task<long> CreateEmployeeRequestAsync(EmployeeRequestCreateRequest request, CancellationToken cancellationToken = default) {
if(request == null) throw new ArgumentNullException(nameof(request));
const string path = "employeerequest";
return (await this.PostAsync<EmployeeRequestCreateRequest, EmployeeRequestCreateResponse>(path, request, cancellationToken).ConfigureAwait(false)).RequestID;
}

Related

Polly doesn't handle an exception in a task because of Task.WhenAny

When our connection drops, ReceiveAsync is throwing WebSocketException (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely).
The issue is that it's not handled by Polly for some reason. I believe it doesn't handle it, because it's in a separate Task, although I'm doing Task.WhenAny.
The expected behavior is to trigger the reconnect if WebSocketException is thrown.
public sealed class ChannelWebSocketClient : IDisposable
{
private readonly Uri _uri;
private readonly ILogger<ChannelWebSocketClient> _logger;
private readonly Channel<string> _output;
private CancellationTokenSource? _cancellationTokenSource;
public ChannelWebSocketClient(Uri uri, ILoggerFactory loggerFactory)
{
_uri = uri ?? throw new ArgumentNullException(nameof(uri));
_logger = loggerFactory.CreateLogger<ChannelWebSocketClient>();
_output = Channel.CreateUnbounded<string>(new UnboundedChannelOptions
{
SingleReader = true,
SingleWriter = false
});
}
public void Dispose()
{
_output.Writer.TryComplete();
}
public Task StartAsync()
{
return Policy.Handle<Exception>(ex => ex is not (TaskCanceledException or OperationCanceledException))
.WaitAndRetryForeverAsync(
(_, _) => TimeSpan.FromSeconds(5),
(ex, retryCount, calculatedWaitDuration, _) => { _logger.LogError(ex, "Unable to connect to the web socket server. Retry count: {RetryCount} | Retry in {Seconds} seconds", retryCount, calculatedWaitDuration.TotalSeconds); })
.ExecuteAsync(ConnectAsync);
}
public void Stop()
{
_cancellationTokenSource?.Cancel();
}
private async Task ConnectAsync()
{
_logger.LogDebug("Connecting");
using var ws = new ClientWebSocket();
// WebSocketException, TaskCanceledException
await ws.ConnectAsync(_uri, CancellationToken.None).ConfigureAwait(false);
_logger.LogDebug("Connected to {Host}", _uri.AbsoluteUri);
_cancellationTokenSource = new CancellationTokenSource();
var receiving = ReceiveLoopAsync(ws, _cancellationTokenSource.Token);
var sending = SendLoopAsync(ws, _cancellationTokenSource.Token);
var trigger = await Task.WhenAny(receiving, sending).ConfigureAwait(false);
if (trigger == receiving)
{
_cancellationTokenSource?.Cancel();
await sending.ConfigureAwait(false);
}
_logger.LogDebug("END");
}
public async Task SendAsync(string message)
{
await _output.Writer.WriteAsync(message, CancellationToken.None).ConfigureAwait(false);
}
private async Task SendLoopAsync(WebSocket webSocket, CancellationToken cancellationToken)
{
_logger.LogDebug("SendLoopAsync BEGIN");
try
{
while (await _output.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
{
while (_output.Reader.TryRead(out var message))
{
// WebSocketException, TaskCanceledException, ObjectDisposedException
await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(message)),
WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException)
{
}
finally
{
_logger.LogDebug("SendLoopAsync END");
}
}
private async Task ReceiveLoopAsync(WebSocket webSocket, CancellationToken cancellationToken)
{
_logger.LogDebug("ReceiveLoopAsync BEGIN");
try
{
while (true)
{
ValueWebSocketReceiveResult receiveResult;
using var buffer = MemoryPool<byte>.Shared.Rent(4096);
await using var ms = new MemoryStream(buffer.Memory.Length);
do
{
// WebSocketException, TaskCanceledException, ObjectDisposedException
receiveResult = await webSocket.ReceiveAsync(buffer.Memory, cancellationToken).ConfigureAwait(false);
if (receiveResult.MessageType == WebSocketMessageType.Close)
{
break;
}
await ms.WriteAsync(buffer.Memory[..receiveResult.Count], cancellationToken).ConfigureAwait(false);
} while (!receiveResult.EndOfMessage);
ms.Seek(0, SeekOrigin.Begin);
if (receiveResult.MessageType == WebSocketMessageType.Text)
{
using var reader = new StreamReader(ms, Encoding.UTF8);
var message = await reader.ReadToEndAsync().ConfigureAwait(false);
_logger.LogInformation("Message received: {Message}", message);
}
else if (receiveResult.MessageType == WebSocketMessageType.Close)
{
break;
}
}
}
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely)
{
_logger.LogError(ex, "");
throw;
}
finally
{
_logger.LogDebug("ReceiveLoopAsync END");
}
}
}
The Task.WhenAll works differently than Task.WhenAny.
Former throws exception is any of the tasks failed with an exception
Latter does not throw exception even if all of the tasks fail
So either you use call two twice the .GetAwaiter().GetResult() since WhenAny returns a Task<Task>
Task.WhenAny(receiving, sending).ConfigureAwait(false)
.GetAwaiter().GetResult()
.GetAwaiter().GetResult();
Or you can re-throw the exception
var trigger = await Task.WhenAny(receiving, sending).ConfigureAwait(false);
if (trigger.Exception != null)
{
throw trigger.Exception;
}
None of these solutions is perfect, but they will trigger your policy.
UPDATE #1
As Monsieur Merso pointed out you can call twice await
await await Task.WhenAny(receiving, sending).ConfigureAwait(false);
This is much better than the above two approaches.
UPDATE #2
If you want to
trigger the policy if faster task failed
or want to know which one has finished sooner with success
then you can "avoid" the double await
var trigger = await Task.WhenAny(receiving, sending).ConfigureAwait(false);
await trigger; //Throws exception if the faster Task has failed
if (trigger == receiving) //Determines which Task finished sooner
{
}

HttpClient How to catch every response in one place

I'm working on Xamarin.Forms App connected with Web Api 2 Api and all requests and responses work with HttClient. This is my code:
class for all my requests and definiot of HttpClient
public class DataStore : IDataStore<object>
{
HttpClient client;
public DataStore()
{
client = new HttpClient()
{
BaseAddress = new Uri($"{App.Uri}")
};
}
Example of one of my requests :
public async Task<User> GetProfileSetup()
{
try
{
if (CrossConnectivity.Current.IsConnected)
{
string token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(#"api/User/GetProfilSetup");
if (response.IsSuccessStatusCode)
{
string jsonMessage;
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
jsonMessage = new StreamReader(responseStream).ReadToEnd();
}
User user = JsonConvert.DeserializeObject<User>(jsonMessage);
return user;
}
else
{
var m = response.Content.ToString();
return null;
}
}
else
{
return null;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
string error = ex.Message;
return null;
}
}
My idea is to check every response(Response Status Code) in one place. I need this for throw Alert Errors , for refresh token etc. Is there a possible way to this ? I want to have control on every request/response.
if anyone have problem with this , just need to implement custom handler , who will inherit form DelegatingHandler. My code example:
public class StatusCodeHandler : DelegatingHandler
{
public StatusCodeHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
public GetStatusCode GetStatusCode = new GetStatusCode();
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
response = await base.SendAsync(request, cancellationToken);
if (response.IsSuccessStatusCode)
{
return response;
}
else
{
var status_code = (int)response.StatusCode;
GetStatusCode.GetResponseCode(status_code);
}
return response;
}
}
This is not related to xamarin, its a question of abstraction in OOP. You can and should abstract HttpClient and its methods to remove all the boilerplate.
Example - GetAsync<T>(url) will check for connectivity, forms request adds necessary headers, waits for response, checks response status, reads response and finally returns the deserialised response. That way, if you want to add caching layer it's easier. Basic OOP.
Abstracting your code:
public async Task<T> GetAsync(string url)
{
try
{
if (!CrossConnectivity.Current.IsConnected)
{
// throw custom exception?
new NoNetworkException();
}
var token = DependencyService.Get<ISharedFunctions>().GetAccessToken();
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
var response = await client.GetAsync(url);
if (!response.IsSuccessStatusCode)
{
// read response and throw for logging?
new InvaidResponseException();// custom exceptions makes it easier for catching
}
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
// there should be an async overload to read too
var jsonMessage = new StreamReader(responseStream).ReadToEnd();
return JsonConvert.DeserializeObject<T>(jsonMessage);
}
}
catch(NoNetworkException ex)
{
// handle
}
catch(InvaidResponseException ex)
{
// handle
}
}

Cannot send the same request message multiple times using Func<HttpRequestMessage>

Our API has retry logic for calling another endpoint. But it kept giving me an error of
The request message was already sent. Cannot send the same request
message multiple times
Here's my code
public async Task<object> GetResponse()
{
var httpRequestMessage = ConstructHttpRequestForBatchUpdate(batchRequest, client, requestUri);
HttpResponseMessage httpResponseMessage = await _retryHttpRequest.ExecuteAsync(() => httpRequestMessage, client, maxRetryValue);
}
private HttpRequestMessage ConstructHttpRequestForBatchUpdate(JArray batchRequest, HttpClient client, Uri requestUri)
{
var batchReqStr = JsonConvert.SerializeObject(batchRequest);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var httpRequestMessage = new HttpRequestMessage()
{
Content = new StringContent(batchReqStr, Encoding.UTF8, "application/json"),
Method = HttpMethod.Put,
RequestUri = requestUri
};
return httpRequestMessage;
}
public class RetryHttpRequest : IRetryHttpRequest
{
public async Task<HttpResponseMessage> ExecuteAsync(Func<HttpRequestMessage> requestMessage, HttpClient client, int maxTryValue)
{
var remainingTries = maxTryValue;
var exceptions = new List<Exception>();
do
{
--remainingTries;
try
{
return await ExecuteSingleAsync(requestMessage(), client);
}
catch (Exception e)
{
exceptions.Add(e);
}
}
while (remainingTries > 0);
throw new AggregateException(exceptions);
}
public async Task<HttpResponseMessage> ExecuteSingleAsync(HttpRequestMessage requestMessage, HttpClient client)
{
try
{
HttpResponseMessage httpResponseMessage = await client.SendAsync(requestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
return httpResponseMessage;
}
else
{
var exception = new InvalidOperationException();
throw new Exception();
}
}
catch (HttpRequestException httpException)
{
throw httpException;
}
}
}
To my understanding, Func<HttpRequestMessage> allows it to create a new instance of HttpRequestMessage. For example, for this line of code
return await ExecuteSingleAsync(requestMessage(), client);
requestMessage() is creating a new instance for every loop. But if my understanding is correct, i am not sure why it is still giving me this error of sending the same request.
requestMessage() is creating a new instance for every loop.
This is not the case - the Func you handed in is () => httpRequestMessage, which always returns the same instance.
Try this instead:
_retryHttpRequest.ExecuteAsync(() => ConstructHttpRequestForBatchUpdate(batchRequest, client, requestUri), client, maxRetryValue);

HttpActionExecutedContext response content always is empty

When throws an exception in server side, the response content in OnException works well but in client side (SendAsync) it is empty..
public override void OnException(HttpActionExecutedContext context)
{
var exception = context.Exception;
if (exception == null) return;
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
context.Response.Content = new StringContent(exception.Message);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
var responseBodyAsText = string.Empty;
try
{
response = await base.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
responseBodyAsText = await response.Content.ReadAsStringAsync();
}
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex)
{
throw new BusinessException(responseBodyAsText);
}
return response;
}

Abort GetResponseAsync() and Exception in Task

I have written a method which get ETag from eq. XML file on server. Am I correct wrote abort task if timeout (GetResponseAsync() doesn't have CancellationToken) and I have no other idea how to do Exception.
Here's code:
public static async Task<string> GetETagAsync(Uri feedLink)
{
const int millisecondsTimeout = 2500;
WebRequest webRequest = WebRequest.Create(feedLink);
webRequest.Method = "HEAD";
try
{
Task<WebResponse> webResponse = webRequest.GetResponseAsync();
if (await Task.WhenAny(webResponse, Task.Delay(millisecondsTimeout)) == webResponse)
{
using (var result = webResponse.Result)
{
return result.Headers["ETag"];
}
}
else
{
webRequest.Abort();
return null;
}
}
catch (Exception)
{
return null;
}
}
Edit
I have made some changes. Rewrite exceptions and use class from this topic: GetResponseAsync does not accept cancellationToken
Code:
public static async Task<string> GetETagAsync(Uri feedLink)
{
const int millisecondsTimeout = 2500;
var cancellationTokenSource = new CancellationTokenSource();
WebRequest webRequest = WebRequest.Create(feedLink);
webRequest.Method = "HEAD";
try
{
Task<WebResponse> webResponse = WebRequestExtensions.GetResponseAsync(webRequest, cancellationTokenSource.Token);
if (await Task.WhenAny(webResponse, Task.Delay(millisecondsTimeout)) == webResponse)
{
using (var result = webResponse.Result)
{
return result.Headers["ETag"];
}
}
else
{
cancellationTokenSource.Cancel();
return null;
}
}
catch (AggregateException ex)
{
if (ex.InnerException is WebException)
return null;
throw;
}
}
public static class WebRequestExtensions
{
public static async Task<WebResponse> GetResponseAsync(this WebRequest request, CancellationToken cancellationToken)
{
using (cancellationToken.Register(() => request.Abort(), useSynchronizationContext: false))
{
try
{
var response = await request.GetResponseAsync();
cancellationToken.ThrowIfCancellationRequested();
return (WebResponse)response;
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.RequestCanceled)
{
cancellationToken.ThrowIfCancellationRequested();
}
if (cancellationToken.IsCancellationRequested)
{
throw new TaskCanceledException(ex.Message, ex);
}
throw;
}
}
}
}
Is it correct now?
You can use the WebRequestExtensions class I wrote here to resolve the problem:
https://github.com/openstacknetsdk/openstack.net/blob/master/src/corelib/Core/WebRequestExtensions.cs
This class fully supports both a CancellationToken and the WebRequest.Timeout property.
Since the method doesn't natively support any means of stopping the task, it can't be stopped. There is nothing that you can do that will prevent it from continuing to do whatever it plans to do. What you can do is ensure that this async method's task doesn't wait for it to finish. While you can just return a value, as you're doing, this seems to represent an exceptional case; you should probably be throwing an exception to indicate that the operation timed out.

Categories