Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mvc Application Async Methods Are Hanging

We have SOA for our solution. We are using .net framework 4.5.1, asp.net mvc 4.6, sql server, windows server and thinktecture identity server 3 ( for token based webapi calls. )

Solution structure looks like;
enter image description here

Our mvc frontend application talks with our webapi application via a httpClient wrapper. Here is the generic http client wrapper code;

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace Cheetah.HttpClientWrapper
{
    public class ResourceServerRestClient : IResourceServerRestClient
    {
        private readonly ITokenProvider _tokenProvider;

        public ResourceServerRestClient(ITokenProvider tokenProvider)
        {
            _tokenProvider = tokenProvider;
        }

        public string BaseAddress { get; set; }

        public Task<T> GetAsync<T>(string uri, string clientId)
        {
            return CheckAndInvokeAsync(async token =>
            {
                using (var client = new HttpClient())
                {
                    ConfigurateHttpClient(client, token, clientId);

                    HttpResponseMessage response = await client.GetAsync(uri);

                    if (response.IsSuccessStatusCode)
                    {
                        return await response.Content.ReadAsAsync<T>();
                    }

                    var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}");
                    exception.Data.Add("StatusCode", response.StatusCode);
                    throw exception;
                }
            });
        }

        private void ConfigurateHttpClient(HttpClient client, string bearerToken, string resourceServiceClientName)
        {
            if (!string.IsNullOrEmpty(resourceServiceClientName))
            {
                client.DefaultRequestHeaders.Add("CN", resourceServiceClientName);
            }

            if (string.IsNullOrEmpty(BaseAddress))
            {
                throw new Exception("BaseAddress is required!");
            }

            client.BaseAddress = new Uri(BaseAddress);
            client.Timeout = new TimeSpan(0, 0, 0, 10);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
        }

        private async Task<T> CheckAndInvokeAsync<T>(Func<string, Task<T>> method)
        {
            try
            {
                string token = await _tokenProvider.IsTokenNullOrExpired();

                if (!string.IsNullOrEmpty(token))
                {
                    return await method(token);
                }

                var exception = new Exception();
                exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized);
                throw exception;
            }
            catch (Exception ex)
            {
                if (ex.Data.Contains("StatusCode") && ((HttpStatusCode)ex.Data["StatusCode"]) == HttpStatusCode.Unauthorized)
                {
                    string token = await _tokenProvider.GetTokenAsync();

                    if (!string.IsNullOrEmpty(token))
                    {
                        return await method(token);
                    }
                }

                throw;
            }
        }

        public void ThrowResourceServerException(List<string> messages)
        {
            string message = messages.Aggregate((p, q) => q + " - " + p);

            var exception = new Exception(message);

            exception.Data.Add("ServiceOperationException", message);

            throw exception;
        }
    }
}

Also, sometimes this http client wrapper using with NitoAsync manager ( Call async methods as sync. ), and sometimes we are using this generic method directly with await - async task wait like;

var result = await _resourceServerRestClient.GetAsync<ServiceOperation<DailyAgendaModel>>("dailyAgenda/" + id);

So here is our problem:

When we test our mvc application with jmeter (for making some-kind-of load test / 10 threads per 1 sec), after a couple of minutes, mvc application stops working [ exception is task canceled due to timeout ] ( maybe only 1-2 requests timeouts ) on this line: HttpResponseMessage response = await client.GetAsync(uri);. But after that request, all requests will be failed like they are in row. So mvc application is hanging for 2-15 minutes ( randomly ) but in that time I can send new requests from postman to webapi. They are ok, I mean webapi is responding well. After a couple of minutes mvc application turnback to normal.

Note: We have load-balancer for mvc-ui and webapi. Because sometimes we get 120K requests in a minute in a busy day. But it gives same error if there is no load balancer in front of webapi or mvc application. So it's not LB problem.

Note2: We tried to use RestSharp for mvc-ui and webapi communication. We got same error here. When a reuqest is failing, all requests will be failed in a row. It looks like it's a network error but we can't find a proof for it.

Can you see any error on my httpClient wrapper ? or better question is;
In your solution, how is your mvc application communicating with your webapi application ? What are the best practices here ?

Update1: We moved projects .net 4.5.1 to 4.6.1. Same deadlock happened again. And than we temporary moved all source codes of the layer: "Business & Repository" as dll level. There is no webapi between business & presentation level now. Dead lock solved. We are still searching why httpClientWrapper codes are not working properly when we called webapi methods from our webapplication controllers.

like image 542
Lost_In_Library Avatar asked Mar 02 '16 17:03

Lost_In_Library


1 Answers

better question is; In your solution, how is your mvc application communicating with your webapi application ? What are the best practices here ?

A best practice here is for the client (browser in your case) to directly retrieve data from the Web API Controllers and for the MVC controllers to only serve pure HTML views which include layout, styles (css), visual structure, scripts (ie. javascript) etc and not the data.

Browser communicating to MVC and Web API

Image credit: Ode to Code. Incidentally the author on that site also does not recommend your approach although it is listed as an option.

  1. This servers as a good SOC between your views and your data allowing you more easily to make changes to either part.
  2. It allows for the client (browser) to retrieve data asynchronously which creates for a better user experience.

By not doing this and adding a network request step in the call stack you have created an unnecessary expensive step in the flow of data (call from MVC Controller(s) to Web API deployment). The more boundaries are crossed during executing the slower the execution.

The fast solution, as you have already figured out, is to call your business code library directly from your MVC project. This will avoid the additional and unnecessary network step. There is nothing wrong with doing this and many more traditional sites serve both the view (html) and data in the same call. It makes for a more tightly coupled design but it is better than what you had.

The best long term solution is to change the MVC views so they call your Web API deployment directly. This can be done using frameworks like Angular, React, Backbone, etc. If the Web API method calls are limited and are not expected to grow you can also use JQuery or pure javascript BUT I would not try to build a complex application on this, there is a reason why frameworks like Angular have become so popular.

As to the actual underlying technical problem in this case we can't be sure without a memory dump to see what resources are causing the deadlock. It might be something as simple as making sure your MVC Action Methods are also returning async Task<ActionResult> (instead of just ActionResult which, I am guessing, is how you have them structured now) so they can call the HttpClient using an actual async/await pattern. Honestly, because its a bad design, I would not spend any time into trying to get this to work.

like image 191
Igor Avatar answered Oct 20 '22 00:10

Igor