2

I developed C# .net 4.6.1 application (this is Windows Service Application). In the application I use HttpClient for communicate with our backend API. Over time the application not sending the request to our backend (TaskCanceledException is catched).

Here is stacktrace of this exception

System.Threading.Tasks.TaskCanceledException · A task was cancelled.

System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
TestHttpClient+<GetRequestAsync>d__20.MoveNext()

This issue solves in two reason

  1. after restart application
  2. When attempt the same request repeatedly

I investigated this issue using Fiddler, when an exception occurs and in normal condition

  1. In normal mode fiddler shows 2 requests
  • Result=200, Protocol=HTTP, Host = Tunnel to, Url = api.example.com:443

in SyntaxView tab: A SSLv3-compatible ClientHello handshake was found. Fiddler extracted the parameters below.

  • Result=200, Protocol=HTTPS, Host = api.example.com, Url = /test
  1. In failed mode Fiddler shows only 1 requst
  • Result=200, Protocol=HTTP, Host = Tunnel to, Url = api.example.com:443,

in SyntaxView tab: After the client received notice of the established CONNECT, it failed to send any data

Here is the snippet of our code.

class Program
{
  static void Main(string[] args)
  {
    // I Tried also without  Task.Run() i.e. MyClass myClass = GetAsync().Result; 
    MyClass myClass = Task.Run(() => GetAsync().Result).Result ;
  }
        
  private static async Task<MyClass> GetAsync()
  {
    // I Tried also without .ConfigureAwait(false)
     MyClassResponse myClassResponse = await TestHttpClient.GetMyClassResponse().ConfigureAwait(false);
    return MyClass.Create(myClassResponse);
  }
}
public static class TestHttpClient
{
   private static HttpClient _httpClient;
   
   public static void Init()
   {
      ServicePointManager.SecurityProtocol |= (SecurityProtocolType.Ssl3 | 
                SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | 
                                             SecurityProtocolType.Tls);
      ServicePointManager.DefaultConnectionLimit = 10;
     _httpClient = CreateHttpClient();
   }

   private static HttpClient CreateHttpClient()
   {
      HttpClient client = new HttpClient();
      client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token .....");
      client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
      client.Timeout = TimeSpan.FromMilliseconds(10000);
      return client;
    }

   public static async Task<MyClassResponse> GetMyClassResponse()
   {
      HttpResponseMessage response = await GetRequestAsync("https://api.example.com/test");
      return await ParseToMyClassResponse<MyClassResponse>(response);
   }
 
    private static async Task<HttpResponseMessage> GetRequestAsync(string url)
   {
        try
        {
           return await _httpClient.GetAsync(new Uri(url));
         }
         catch (TaskCanceledException)
         {
          return new HttpResponseMessage(HttpStatusCode.RequestTimeout);
         }
    }

    private static async Task<T> ParseToMyClassResponse<T>(HttpResponseMessage response) where T : MyClassResponse, new()
        {
            T myClassResponse;
            try
            {
                string content = await response.Content.ReadAsStringAsync();
                response.EnsureSuccessStatusCode();
                myClassResponse = JsonConvert.DeserializeObject<T>(content);
                           }
            catch (Exception ex)
            {
                myClassResponse = new T();
            }

            response.Dispose();
            return myClassResponse;
        }
}

What am I doing wrong?

Why Fiddler shows "After the client received notice of the established CONNECT, it failed to send any data" message.

ahak
  • 31
  • 1
  • 5
  • 3
    You should not use `SecurityProtocolType.Ssl3` anymore. SSLv3 is heavily insecure and outdated. Most servers even no longer support the SSLv3 client hello. – Robert Jun 26 '20 at 10:11
  • Hi @Robert . Thanks for quick response. We had a `System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel`. And after adding `SecurityProtocolType.Ssl3` the exception has disappeared. If the exception is related to `SecurityProtocolType.Ssl3`, then why does this happen sometimes. – ahak Jun 26 '20 at 11:06
  • Is the server a public server on the Internet or a private one in your Intranet? If you are in control of the server it should be checked because it is heavily insecure if it requires an SSLv3 handshake. I assume this is also the problem with Fiddler. Depending on the used Windows version Fiddler also does not use the SSLv3 handshake and therefore the connection to the server can not be established. – Robert Jun 26 '20 at 11:24
  • @Robert The server is public. And on the server we use `TLS`. I do not know why, but without `SecurityProtocolType.Ssl3` we had `System.Net.WebException` exception. I want to note that the `System.Net.WebException` exception did not always occur. Ok, I will try to remove `SecurityProtocolType.Ssl3`, if the `System.Net.WebException` exception will reappear. I`ll open a new discussion. Could you explain to me why this error does not always occur? – ahak Jun 26 '20 at 12:36
  • 2
    If he server is public I would recommend to perform an SSL test on it: https://www.ssllabs.com/ssltest/ Also keep in mind that for Dot.net programs the SSL/TLS implementation of Windows is used. hence if you use an outdated OS like Win7 or XP it might fail just because of the OS. – Robert Jun 27 '20 at 15:55
  • @Aram There are several duplicate questions about this already. In *supported* OS versions, and .NET Core or recent .NET Old versions the runtime will use the best available encryption. If your server requires SSLv3 it means it's running on an unsupported OS at least, probably an older or unpatched .NET runtime too. You'll have to install the appropriate patches and registry settings to ensure current encryption settings are used – Panagiotis Kanavos Jun 30 '20 at 10:56
  • @Aram btw even StackExchange [removed support for TLS 1.1 and lower](https://meta.stackexchange.com/questions/343302/tls-1-0-and-tls-1-1-removal-for-stack-exchange-services). You should ensure your server uses TLS1.2. This is actually a requirement in many domains, eg PCI requires sites use TLS 1.2 or higher on e-commerce sites. Not just for payments either – Panagiotis Kanavos Jun 30 '20 at 11:02
  • @Aram use .NET 4.7 at least to use TLS1.2 by default. In some 4.6.x versions you had to explicitly enable TLS 1.2. Those versions were a mess to use with .NET Standard too, so it's far better to upgrade to the latest .NET version than try to find the issues in 4.6 – Panagiotis Kanavos Jun 30 '20 at 11:04
  • Hi @PanagiotisKanavos. We cannot use `.Net 4.7` because we are integrated with other services. These services require us to use `.Net 4.6.1`.Our server using `TLS1.2`. [Here the screen](https://drive.google.com/file/d/1SRFyGNppZSLiJ-kxLyB1ZruB1VjSoki2/view?usp=sharing), and [this screen](https://drive.google.com/file/d/1euqB_UsR_d8aa-2yRM11konyZ6tfjs9L/view?usp=sharing). – ahak Jun 30 '20 at 11:49
  • Yes you can, and you probably are already. .NET 4.x versions are all binary replacements of previous ones. If Windows Update or some other application installed a 4.7 version, you're already running on 4.7. Services don't care about the runtime or language of their callers either, just the IPC protocol. If you mean libraries, you *can* use libraries that came from other projects – Panagiotis Kanavos Jun 30 '20 at 11:55
  • @Aram if you want to use 4.6, you need to explicitly set it to use TLS1.2, as shown in the duplicate, by setting `ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12` – Panagiotis Kanavos Jun 30 '20 at 11:56
  • @PanagiotisKanavos in the `Init()` method before initialization of `httpClient` i explicitly set `TLS1.2`. Isn't it? My question is about `TaskCanceledException`. Sometimes `TaskCanceledException` is catched. Do you think that cause of `TaskCanceledException` is in this `ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12`? – ahak Jun 30 '20 at 14:16
  • @Αram it's impossible to read a question with so much code. It's also a *lot* harder to debug such code. Yes, but you also enabled the deprecated protocols. Start with a *new* simple, empty Console application that only calls that server, containing only the relevant code. You shouldn't need more than 10 lines to do this. Use `async Task Main()` instead of `void Main()` so you don't have to use `.Result` either. That `Task.Run` isn't needed anyway. If you still have an issue post that minimal example – Panagiotis Kanavos Jun 30 '20 at 14:24
  • BTW the way you use HttpClient is wrong. Yes, HttpClient is meant to be reused but *not* indefinitely. It will cache sockets and connections which means it won't detect DNS changes and keep trying to connect to the same IP. This can cause problems with load balancers, server farms etc. Perhaps that's what's happening here? This is described in Singleton HttpClient? Beware of this serious behaviour and how to fix it[](http://byterot.blogspot.co.uk/2016/07/singleton-httpclient-dns.html) – Panagiotis Kanavos Jun 30 '20 at 14:29
  • Even that problem is solved through the [HttpClientFactory](https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore) which was introduced along with .NET Core 2.1. It's a .NET Standard 2.0 library, which means it can be used by .NET Framework applications too. If you target .NET 4.6 though, you may get into trouble [with compatibility assemblies](https://learn.microsoft.com/en-us/dotnet/standard/net-standard). `For .NET Framework projects that need to use such libraries, we recommend that you upgrade the project to target .NET Framework 4.7.2 or higher.` – Panagiotis Kanavos Jun 30 '20 at 14:34

1 Answers1

2
  1. First of all, replace this
static void Main(string[] args)
{
    // I Tried also without  Task.Run() i.e. MyClass myClass = GetAsync().Result; 
    MyClass myClass = Task.Run(() => GetAsync().Result).Result ;
}

with whis

static async Task Main(string[] args)
{
    MyClass myClass = await GetAsync();
    
    Console.WriteLine("All done.");
    Console.ReadKey();
}
  1. HttpResponseMessage is IDisposable, use disposing properly
public static async Task<MyClassResponse> GetMyClassResponse()
{
    using (HttpResponseMessage response = await GetRequestAsync("https://api.example.com/test"))
    {
        return await ParseToMyClassResponse<MyClassResponse>(response);
    }
}

And remove response.Dispose() from ParseToMyClassResponse

  1. For additional test I've rewritten the code to make it simpler.
public class Program
{
    private readonly static HttpClient client = new HttpClient();

    static async Task Main(string[] args)
    {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token .....");
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        client.Timeout = TimeSpan.FromMilliseconds(10000);
        ServicePointManager.DefaultConnectionLimit = 10;

        try
        {
            MyClassResponse myClassResponse = await GetAPIResponseAsync("https://api.example.com/test");
            MyClass myClass = MyClass.Create(myClassResponse);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine();
            Console.WriteLine(ex.StackTrace);
        }

        Console.WriteLine("All done.");
        Console.ReadKey();
    }

    private static async Task<T> GetAPIResponseAsync<T>(string url)
    {
        using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
        {
            response.EnsureSuccessStatusCode();
            string content = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<T>(content);
        }
    }
}

Test this version.

Additionally it's not recommended to change SecurityProtocol in code because it makes the app vulnerable, use default instead. If you want to change TLS version for test, edit Windows Registry.


Updated

I've looked at the source.

This is a kind of bad concurrent programming. You run async function in task and block task where it being executed and block current thread at the same time.

Task<PassResponse> task = Task.Run(() => GetPass(scannedValue).Result);
PassResponse passResponse = task.Result;

You may reach the same result this way

PassResponse passResponse = GetPass(scannedValue).Result;

One more try

let's change this line to avoid possible deadlocks

return await _httpClient.GetAsync(new Uri(url));

to

return await _httpClient.GetAsync(new Uri(url)).ConfigureAwait(false);

And this one

string content = await response.Content.ReadAsStringAsync();

to

string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
aepot
  • 4,558
  • 2
  • 12
  • 24
  • Hi @aepot. Thank you very much for rewritten code, but i showed only snippet from my project, therefore i couldn't use your code. As for the first comment: I need to lock the code in this place, so i used `.Result` (But i will think how to avoid of code blocking). I had changed the `SecurityProtocol`, because i catched `System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel`. Do you have any idea why this exception is thrown? – ahak Jun 28 '20 at 08:08
  • @Aram if you use `.Result` or `.GetAwaiter().GetResult()`, it means that something went wrong. It called `sync-over-async` approach and it's the easiest way to get a deadlock. Avoid locking the main thread. For SSL, at least drop insecure SSLv3 from the list. – aepot Jun 28 '20 at 08:51
  • @Aram pay attention to `ResponseHeadersRead` and `EnsureSuccessStatusCode()` which moved before loading the data. – aepot Jun 28 '20 at 08:55
  • @Aram can you show the real method with `.Result`? I'm sure that it can be rewritten as `async` one. – aepot Jun 28 '20 at 09:56
  • Hi @aepot. I am grateful for your help. The project is too huge, therefore i cut it and left only important pieces of code.We integrated with other services. The services is communicating with our plugin via Http request. For some reason we must do it on local computer. Theierfore we used Nancy framework for providing communication. – ahak Jun 29 '20 at 13:56
  • Here is [poject](https://drive.google.com/file/d/1oGK7kBQ_PI09jZQ-rtJGtwAz5TIetMmV/view). @aepot I would like introduce, how it work. It all starts when the client application sends httpRequest to `"http://localhost:2222//api/op/checkin"`. Our plugin catches this request in `ExtendedNancyModule`. After it our plugin calls to our API and response to client. I blocked `async` call in order to delay response of client. – ahak Jun 29 '20 at 13:56
  • @Aram added one more suggestion to the answer. – aepot Jun 29 '20 at 14:38
  • @Aram will look deeper in the code. Now i see simply that my above suggestions were not applied. Give it a try. – aepot Jun 29 '20 at 14:44
  • Thanks @aepot for quick response. Earlier i have used `GetPass(scanned Value).Result`. But I thought that this code may be cause of deadlock and changed it with `Task.Run(() => GetPass(scanned Value).Result).Result`. – ahak Jun 29 '20 at 20:46
  • @Aram and that changed code can cause the same deadlock :) You didn't change anything. – aepot Jun 29 '20 at 20:47
  • Ok @aepot. i will revert this code. But in that time, when i used `GetPass(scannedValue).Result`, this issue also was appeared – ahak Jun 30 '20 at 09:08
  • @Aram I can't help with the issue, sorry. I just tried to fix the snippet you provided in the question. BTW, I'm not familiar with Web-Servers like `NancyModule`, thus I cannot reproduce the issue. Additionally: if you want to run some `async` method in `Task`, you may use the following snippet: `Task.Run(async () => await SomeMethodAsync());`. It looks much better than yet another `.Result`. – aepot Jun 30 '20 at 10:25
  • @Aram one more try to avoid possible deadlocks. Updated the answer. – aepot Jun 30 '20 at 10:45
  • thanks @aepot very much. I appreciate your help. – ahak Jun 30 '20 at 11:03
  • @Aram did it help? – aepot Jun 30 '20 at 11:38
  • .@aepotI tried it earlier. It did not help – ahak Jun 30 '20 at 11:56
  • @Aram probably you have no deadlocks then. So, I'm defeated. – aepot Jun 30 '20 at 12:05