ASP.NET Web API load test problem

Status
Not open for further replies.

raysefo

Well-known member
Joined
Feb 22, 2019
Messages
361
Programming Experience
10+
Hi,

I have a rest asp.net web API. I am load testing with Jmeter. I got this error;

C#:
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

I couldn't detect the location of this error because I haven't deployed the PDBs yet. But I wonder there is this basic authentication filter in my web API which queries the database for the client. This query is NOT async, could this be a reason?

C#:
public class UserValidate : IUserValidate
    {
        private readonly UnitOfWork _unitOfWork;

        /// <summary>
        /// Public constructor.
        /// </summary>
        public UserValidate(UnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork;
        }

        /// <summary>
        /// Public method to authenticate user by user name and password.
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public bool Login(string userName, string password)
        {
            var user = _unitOfWork.UserRepository.Get(u => u.userName.Equals(userName, StringComparison.OrdinalIgnoreCase) && u.password == password);
            if (user == null) return false;
            return true;
        }
    }

Stack trace indicates this method below but there are no such compilation warnings (cs4014) and I couldn't find any missing await in this code below;

C#:
private async Task<HttpResponseMessage> CallRazerService(RequestDto requestDto)
        {
            HttpResponseMessage response = null;
            using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
            {
                //Transform DTO into GameRequest for calling Razer Initiate
                var config = new MapperConfiguration(cfg =>
                {
                    cfg.CreateMap<RequestDto, GameRequest>();
                    cfg.CreateMap<GameRequest, GameConfirmRequest>();
                    cfg.CreateMap<GameConfirmResponse, GameConfirmResponseDto>();
                    cfg.CreateMap<Coupon, CouponDto>();
                });
                var iMapper = config.CreateMapper();
                var gameRequest = iMapper.Map<RequestDto, GameRequest>(requestDto);
                //Unique reference ID
                gameRequest.referenceId = Guid.NewGuid().ToString();
                //Create signature
                gameRequest = Utilities.CreateSignature(gameRequest, RequestType.Initiate);


                //Add initiation request into database
                _unitOfWork.GameRepository.Insert(gameRequest);

                #region Call Razer initiate/confirm

                //Call Razer for initiation
                response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");

                //Read response
                var htmlResponse = await response.Content.ReadAsStringAsync();
                var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);

                //Adding initiation response into database
                _unitOfWork.GameResponseRepository.Insert(gameResponse);

                if (gameResponse.initiationResultCode == "00")
                {
                    gameRequest.validatedToken = gameResponse.validatedToken;
                    //Create signature
                    var gameConfirmRequest = Utilities.CreateSignature(gameRequest, RequestType.Confirm);

                    //Transform DTO into GameRequest for calling Razer Initiate
                    var gameConfirmRequests = iMapper.Map<GameRequest, GameConfirmRequest>(gameConfirmRequest);

                    //Add confirm request into database
                    _unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);

                    //Call Razer for confirm
                    response = await Utilities.CallRazer(gameRequest, "purchaseconfirmation");

                    //Read response
                    htmlResponse = await response.Content.ReadAsStringAsync();
                    var gameConfirmResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(htmlResponse);

                    //Add confirm response into database
                    _unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);

                }

                #endregion

                await _unitOfWork.SaveAsync();
                scope.Complete();
            }

            return response;
        }

Any guidance will be appreciated :)
 
What do you mean by parallel? There are no parallel queries in the code above. This happens when I load testing.

I simplified my code, is there any parallel query that I missed?

C#:
private async Task<HttpResponseMessage> CallRazerService(RequestDto requestDto)
        {
            HttpResponseMessage response = null;
            using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
            {
              
               //Add initiation request into database
                _unitOfWork.GameRepository.Insert(gameRequest);

                #region Call Razer initiate/confirm

                //Call Razer for initiation
                response = await Utilities.CallRazer(gameRequest, "purchaseinitiation");

                //Read response
                var htmlResponse = await response.Content.ReadAsStringAsync();
                var gameResponse = JsonConvert.DeserializeObject<GameResponse>(htmlResponse);

                //Adding initiation response into database
                _unitOfWork.GameResponseRepository.Insert(gameResponse);

                if (gameResponse.initiationResultCode == "00")
                {
                    
                    //Add confirm request into database
                    _unitOfWork.GameConfirmRequestRepository.Insert(gameConfirmRequests);

                    //Call Razer for confirm
                    response = await Utilities.CallRazer(gameRequest, "purchaseconfirmation");

                    //Read response
                    htmlResponse = await response.Content.ReadAsStringAsync();
                    var gameConfirmResponse = JsonConvert.DeserializeObject<GameConfirmResponse>(htmlResponse);

                    //Add confirm response into database
                    _unitOfWork.GameConfirmResponseRepository.Insert(gameConfirmResponse);

                }

                #endregion

                await _unitOfWork.SaveAsync();
                scope.Complete();
            }

            return response;
        }
 
Last edited:
If you're load testing then you're executing multiple queries. I'd wager that it's the same unit of work in use in each case, thus there will be parallel queries. I did provide two options for a solution.
 
If your unit of work contains the context then to use multiple contexts you need to use multiple units of work. If you have multiple users hitting your service at the same time then they will all have their own unit of work so that's how you ought to be testing.
 
Could it be due to Unity lifetime settings? Someone said, that I am not using the correct lifetime manager. Every mvc request needs a unique dbcontext, the dbcontext can not be shared across requests, or you get concurrent operation errors.


C#:
using System;
using BusinessService;
using DataModels;
using Unity;
using Unity.Lifetime;

namespace Game
{
    /// <summary>
    /// Specifies the Unity configuration for the main container.
    /// </summary>
    public static class UnityConfig
    {
        #region Unity Container
        private static Lazy<IUnityContainer> container =
          new Lazy<IUnityContainer>(() =>
          {
              var container = new UnityContainer();
              RegisterTypes(container);
              return container;
          });

        /// <summary>
        /// Configured Unity Container.
        /// </summary>
        public static IUnityContainer Container => container.Value;
        #endregion

        
        public static void RegisterTypes(IUnityContainer container)
        {
            
            container.RegisterType<IGameServices, GameServices>().RegisterType<UnitOfWork>(new HierarchicalLifetimeManager());
            container.RegisterType<IUserValidate, UserValidate>();
            
       }
    }
}
 
Upon searching executing queries in serial, I found many results. So what do you think you need to do?

Who said this I am not using the correct lifetime manager. and where?

Have you gone over this : unitycontainer/unity
 
since asp.net allows multiple concurrent requests on the same thread, using this one seems more appropriate I think.

ContainerControlledTransientManager

For this lifetime manager Unity creates and returns a new instance of the requested type for each call to the Resolve method. This lifetime manager is similar to TransientManager with the only difference that it holds reference to each created disposable instance and disposes these when container is disposed. This manager is useful when used in session based designs with child container associated with the session.
 
now I am getting this error:

The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: Saving or accepting changes failed because more than one entity of type 'BusinessEntity.GameRequest' have the same primary key value. Ensure that explicitly set primary key values are unique. Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. Use the Entity Designer for Database First/Model First configuration. Use the 'HasDatabaseGeneratedOption" fluent API or 'DatabaseGeneratedAttribute' for Code First configuration.
 
I think this problem is due to transactions. I should use something like this. What are your opinions?
C#:
using (var transaction = _unitOfWork.BeginTransaction())
    {
        try
        {
            Save(entity);
            await _unitOfWork.SaveChangesAsync();

            transaction.Commit();
        }
        catch (DbEntityValidationException e)
        {
        }
    }
 
That warning there has nothing to do with transactions. The issue is with your primary key value management.
 
So as I posted in my other post, can SQL generate GUID for me?
C#:
//Unique reference ID
  gameRequest.referenceId = Guid.NewGuid().ToString();
 
SQL Server can generate GUIDs in exactly the same way that it generates integer IDs, i.e. you create a column of the appropriate type - uniqueidentifier for SQL Server - and then you make that column an identity. That said, a SQL Server table can only have one identity column, so if you are already generating integers that way then you cannot generate GUIDs in the same table. I'm not sure whether SQL Server can generate GUIDs outside of that but, if I wanted to know, I'd open my favourite search engine and type something like "sql server generate guid" and see what came back. I could do that now to try to answer your question but you're just as capable of doing it yourself.
 
Status
Not open for further replies.
Back
Top Bottom