Question Why not errors written to DB?

raysefo

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

I have unhandled exception logger which logs every error into a table. Actually, it works but there is a rare condition when I do a load test with more than 1000 users, errors aren't written into this table. (950 success, 50 errors) Why could this happen? Any ideas? 50 users didn't even start for some reason?

C#:
public class UnhandledExceptionLogger : ExceptionLogger
    {
        public override void Log(ExceptionLoggerContext context)
        {
            var ex = context.Exception;

            string strLogText = "";
            strLogText += Environment.NewLine + "Source ---\n{0}" + ex.Source;
            strLogText += Environment.NewLine + "StackTrace ---\n{0}" + ex.StackTrace;
            strLogText += Environment.NewLine + "TargetSite ---\n{0}" + ex.TargetSite;

            if (ex.InnerException != null)
            {
                strLogText += Environment.NewLine + "Inner Exception is {0}" + ex.InnerException;//error prone
            }
            if (ex.Message != null)
            {
                strLogText += Environment.NewLine + "Message ---\n{0}" + ex.Message;//error prone
            }

            var requestedURi = (string)context.Request.RequestUri.AbsoluteUri;
            var requestMethod = context.Request.Method.ToString();
            var timeUtc = DateTime.Now;

            SqlErrorLogging sqlErrorLogging = new SqlErrorLogging();
            ApiError apiError = new ApiError()
            {
                Message = strLogText,
                RequestUri = requestedURi,
                RequestMethod = requestMethod,
                TimeUtc = DateTime.Now
            };
            sqlErrorLogging.InsertErrorLog(apiError);
        }
    }

C#:
public void InsertErrorLog(ApiError apiError)
        {
            using (var sqlConnection =
                new SqlConnection(ConfigurationManager.ConnectionStrings["GameContext"].ConnectionString))
            {
                try
                {
                    sqlConnection.Open();
                    using (SqlCommand cmd =
                        new SqlCommand(
                            "INSERT INTO [dbo].[API_Error] ([Message],[RequestMethod],[RequestUri],[TimeUtc]) VALUES (@Message, @RequestMethod, @RequestUri, @TimeUtc)",
                            sqlConnection))
                    {
                        cmd.Parameters.AddWithValue("@TimeUtc", apiError.TimeUtc);
                        cmd.Parameters.AddWithValue("@RequestUri", apiError.RequestUri);
                        cmd.Parameters.AddWithValue("@Message", apiError.Message);
                        cmd.Parameters.AddWithValue("@RequestMethod", apiError.RequestMethod);

                        cmd.ExecuteNonQuery();
                    }
                }
                catch (Exception e)
                {
                    throw e;
                }
                finally
                {
                    sqlConnection.Close();
                }
            }
        }
 
Here is the method I tested. Without this (if (gameBankResult != null)) it works as I said in my previous post.

C#:
private async Task<HttpResponseMessage> CallGameNew(RequestDto requestDto)
        {
            HttpResponseMessage response = null;

            //Transform DTO into GameRequest for calling Game Initiate
            var config = new MapperConfiguration(cfg =>
            {
                cfg.CreateMap<RequestDto, GameRequest>();
                cfg.CreateMap<GameRequest, GameConfirmRequest>();
                cfg.CreateMap<GameBank, GameConfirmResponse>();
                cfg.CreateMap<GameBankPin, Coupon>();
                cfg.CreateMap<GameRequest, GameRequestDto>();
            });
            var iMapper = config.CreateMapper();

            var gameRequest = iMapper.Map<RequestDto, GameRequest>(requestDto);
            //Unique reference ID
            gameRequest.referenceId = Guid.NewGuid();

            var gameRequestDto = iMapper.Map<GameRequest, GameRequestDto>(gameRequest);
            //Create signature
            gameRequest = Utilities.CreateSignature(gameRequestDto, RequestType.Initiate);

            //Set service
            gameRequest.service = "OUR";

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

            //Query GameBank database
            var gameBankResult = await _unitOfWork.GameBankRepository.GetGamesAsync(g =>
                g.productCode == requestDto.productCode && g.referenceId == Guid.Empty);
            
            if (gameBankResult != null)
            {
                //Narrow the list based on quantity
                if (gameBankResult.Count > 0)
                {
                    gameBankResult = gameBankResult.Take(requestDto.quantity).ToList();
                }

                if (gameBankResult.Count() >= requestDto.quantity)
                {
                    var k = requestDto.quantity - 1;
                    for (var i = k; i >= 0; --i)
                    {
                        gameBankResult[i].referenceId = gameRequest.referenceId;
                        gameBankResult[i].requestDateTime = DateTime.Now;
                        gameBankResult[i].responseDateTime = DateTime.Now;
                    }

                    //Update GameBank
                    _unitOfWork.GameBankRepository.Update(gameBankResult[k]);

                    var gameBankConfirmResponse =
                        iMapper.Map<IList<GameBank>, IList<GameConfirmResponse>>(gameBankResult);

                    if (requestDto.quantity == 1)
                    {
                        gameBankConfirmResponse[k].purchaseStatusDate = DateTime.Now;

                        var resultResponse = JsonConvert.SerializeObject(gameBankConfirmResponse[k],
                            Formatting.Indented,
                            new JsonSerializerSettings()
                            {
                                ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
                            });
                        response = new HttpResponseMessage
                        {
                            StatusCode = System.Net.HttpStatusCode.OK,
                            Content = new StringContent(resultResponse, System.Text.Encoding.UTF8,
                                "application/json"),
                        };
                        //Set service
                        gameBankConfirmResponse[k].service = "OUR";

                        _unitOfWork.GameConfirmResponseRepository.Insert(gameBankConfirmResponse[k]);
                    }
                    else if (requestDto.quantity > 1)
                    {
                        var gameResult = new GameConfirmResponse {coupons = new List<Coupon>()};
                        var price = 0.0;
                        var quantity = 0;

                        foreach (var item in gameBankResult)
                        {
                            price = price + item.unitPrice;
                            quantity = quantity + 1;
                            foreach (var coupons in item.coupons)
                            {
                                var gameCouponResult = new Coupon()
                                {
                                    expiryDate = coupons.expiryDate, Pin = coupons.Pin, Serial = coupons.Serial
                                };
                                //Add coupon values
                                gameResult.coupons.Add(gameCouponResult);
                            }
                        }

                        //Set summed/counted values
                        gameResult.referenceId = gameBankResult[0].referenceId;
                        gameResult.productCode = gameBankResult[0].productCode;
                        gameResult.quantity = quantity;
                        gameResult.currency = gameBankResult[0].currency;
                        gameResult.unitPrice = gameBankResult[0].unitPrice;
                        gameResult.totalPrice = price;
                        gameResult.productDescription = gameBankResult[0].productDescription;
                        gameResult.purchaseStatusDate = DateTime.Now;
                        gameResult.totalPayablePrice = price;
                        //Set service
                        gameRequest.service = "OUR";

                        //Add confirm response into database
                        _unitOfWork.GameConfirmResponseRepository.Insert(gameResult);
                        var resultResponse = JsonConvert.SerializeObject(gameResult, Formatting.Indented,
                            new JsonSerializerSettings()
                            {
                                ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
                            });
                        response = new HttpResponseMessage
                        {
                            StatusCode = System.Net.HttpStatusCode.OK,
                            Content = new StringContent(resultResponse, System.Text.Encoding.UTF8,
                                "application/json"),
                        };
                    }
                }
            }

            await _unitOfWork.SaveAsync();

            return response;
        }
 
Is your UnitOfWork still the same as the one you presented in your other thread?

Can I correctly assume that the GameContext is simply a subclass of the Entity Framework DbContext?

If so, when is the UnitOfWork being disposed? Is the GameContext that is injected into the UnitOfWork disposed?

Did you note these from the EF documentation?
By default, the context manages connections to the database. The context opens and closes connections as needed.
and
Here are some general guidelines when deciding on the lifetime of the context:
  • When working with Web applications, use a context instance per request.
  • :
  • If the context instance is created by a dependency injection container, it is usually the responsibility of the container to dispose the context.
  • If the context is created in application code, remember to dispose of the context when it is no longer required.
  • When working with long-running context consider the following:
    • :
    • The context is not thread-safe, therefore it should not be shared across multiple threads doing work on it concurrently.
    • :

Note the last bullet above about the context not being thread safe. When you use await in an ASP.NET SynchronizationContext, you have no guarantees that your code will continue running on the same thread pool thread that initially accepted the web request. If you created your GameContext/DbContext on that incoming web request thread, then you now just crossed over to another thread.

See Stephen Cleary's Parallel Computing - It's All About the SynchronizationContext.
:
AspNetSynchronizationContext (System.Web.dll: System.Web [internal class]) The ASP.NET SynchronizationContext is installed on thread pool threads as they execute page code. When a delegate is queued to a captured AspNetSynchronizationContext, it restores the identity and culture of the original page and then executes the delegate directly. The delegate is directly invoked even if it’s “asynchronously” queued by calling Post.

Conceptually, the context of AspNetSynchronizationContext is complex. During the lifetime of an asynchronous page, the context starts with just one thread from the ASP.NET thread pool. After the asynchronous requests have started, the context doesn’t include any threads. As the asynchronous requests complete, the thread pool threads executing their completion routines enter the context. These may be the same threads that initiated the requests but more likely would be whatever threads happen to be free at the time the operations complete.

If multiple operations complete at once for the same application, AspNetSynchronizationContext will ensure that they execute one at a time. They may execute on any thread, but that thread will have the identity and culture of the original page.

One common example is a WebClient used from within an asynchronous Web page. DownloadDataAsync will capture the current SynchronizationContext and later will execute its DownloadDataCompleted event in that context. When the page begins executing, ASP.NET will allocate one of its threads to execute the code in that page. The page may invoke DownloadDataAsync and then return; ASP.NET keeps a count of the outstanding asynchronous operations, so it understands that the page isn’t complete. When the WebClient object has downloaded the requested data, it will receive notification on a thread pool thread. This thread will raise DownloadDataCompleted in the captured context. The context will stay on the same thread but will ensure the event handler runs with the correct identity and culture.
 
By default, the context manages connections to the database. The context opens and closes connections as needed.
I've been saying this since his opening post. Yes the connections are already pre-managed in EF. There have been that many variations of OPs code, It's hard to remember or keep track, but I'm certain at one point the unit or work was being disposed :
public void Dispose() { _unitOfWork.Dispose(); }
There and somewhere else...
//Add initiation request into database _unitOfWork.GameRepository.Insert(gameRequest);
Now if you are disposing it, and then calling it again, that's rather bad programming, and outh to be rethought, especially if not instantiated again. After all it is 05:00 in the morning, I may be right or wrong, and I'm rather to tired to double back on your code for another glance to check, but this might be an issue if the rest of your GameRepository/other is trying to initiate execute another command unless its instantiated again, you will have problems here.

Good night for now
 
Thank you @Skydiver and @Sheepings. I will quit async and try to load test without async.

Is your UnitOfWork still the same as the one you presented in your other thread?
Yes, it is the same UOW.
Can I correctly assume that the GameContext is simply a subclass of the Entity Framework DbContext?
Yes, you are right.
C#:
namespace DataModels
{
    public class GameContext : DbContext
    {
        public GameContext() : base("GameContext")
        {
        }

        public DbSet<GameRequest> Initiates { get; set; }
        public DbSet<GameResponse> InitiatesResponses { get; set; }
        public DbSet<GameConfirmResponse> GameConfirmResponses { get; set; }
        public DbSet<Coupon> Coupons { get; set; }
        public DbSet<GameConfirmRequest> GameConfirmRequests { get; set; }
        public DbSet<GameBank> GameBanks { get; set; }
        public DbSet<GameBankPin> GameBankPins { get; set; }
        public DbSet<ConfirmCancel> ConfirmCancels { get; set; }
        public DbSet<Company> Companies { get; set; }
        public DbSet<Recon> Recons { get; set; }
        public DbSet<ReconDetail> ReconDetails { get; set; }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            //Configure Null Column
            modelBuilder.Entity<GameRequest>()
                .Property(p => p.referenceId)
                .IsOptional();
            modelBuilder.Entity<GameRequest>().Property(t => t.referenceId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        }
    }
}
If so, when is the UnitOfWork being disposed? Is the GameContext that is injected into the UnitOfWork disposed?
It is disposed in GameServices:
C#:
public async Task<HttpResponseMessage> GamePurchase(RequestDto requestDto)
        {
            HttpResponseMessage response = null;
            using (_unitOfWork)
            {
                response = await CallGameNew(requestDto) ?? await CallRazerService(requestDto);
                return response;
            }
        }
Yes, context is injected in UOW.
C#:
namespace DataModels
{
    public class UnitOfWork : IDisposable
    {
        private readonly GameContext _context;
        
        //Some code here

        public UnitOfWork(GameContext context)
        {
            _context = context;
        }
        //Some code here
        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    _context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

Here is Unity config:
C#:
public static void RegisterTypes(IUnityContainer container)
{
            // Default empty Time manager!
            container.RegisterType<IGameServices, GameServices>().RegisterType<UnitOfWork>(new TransientLifetimeManager());
            container.RegisterType<IUserValidate, UserValidate>(new TransientLifetimeManager());
            
}
 
I removed async for database query, update and insert. Just keep async for calling 3rd party rest API. The result is a successful load test with 1500 users :)
 
I tried with 2000 users and got some errors. Somehow they did not write into the database. How can I see the error details? No trace in the event logs either.
 
Somehow they did not write into the database.
Which part of my previous reply did you not understand about doing this. I find it amusing, almost funny that you're experiencing connection pool issues and yet you are trying to log your errors to the database? :unsure: Why are you still trying to insert your errors into the database? Log them to a file man until you sort out your business model.
 
@Sheepings thank you. This time error is not about DB connections. It is as follows:

C#:
StackTrace ---    at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)
TargetSite --- System.Object ChangeType(System.Object, System.Type, System.IFormatProvider)
Message --- Null object cannot be converted to a value type.
--- End Inner Exception ---
Message --- Error converting value {null} to type 'System.Int32'. Path 'quantity', line 1, position 111.

I am trying to figure it out why just this one happened.
 
While you try to figure that out, I recommend also reading the series of blog posts here:
Unity lifetime management for IDisposable, part 1
Unity lifetime management for IDisposable, part 2
Unity lifetime management for IDisposable, part 3

The first part also highlights the same problem I pointed out in the other thread where if you are using dependency injection, you should not be disposing references that were injected into to your object. The first part also confirms your assertion in the other thread that Unity's TransientLifetimeManager doesn't know how to dispose of IDisposable objects.

The reason why I stumble across that blog was that I was flabbergasted when I didn't see objects being disposed in this dead simple code:
C#:
using System;
using Unity;
using Unity.Lifetime;

namespace TestUnityIoC
{
    class A : IDisposable
    {
        public A()
        {
            Console.WriteLine(nameof(A));
        }

        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing)
        {
            Console.WriteLine(nameof(Dispose));
            if (!disposedValue)
            {
                if (disposing)
                {
                }

                disposedValue = true;
            }
        }

        public void Dispose()
        {
            Console.WriteLine(nameof(Dispose));
            Dispose(true);
        }
    }

    interface IB
    {
        void DoIt();
    }

    class B : IB, IDisposable
    {
        A _a;

        public B(A a)
        {
            Console.WriteLine(nameof(B));
            _a = a;
        }

        public void DoIt()
        {
            Console.WriteLine(nameof(DoIt));
        }

        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            Console.WriteLine(nameof(Dispose));
            if (!disposedValue)
            {
                if (disposing)
                {
                    _a.Dispose();
                }

                _a = null;
                disposedValue = true;
            }
        }

        public void Dispose()
        {
            Console.WriteLine(nameof(Dispose));
            Dispose(true);
        }
    }

    class Program
    {
        static void Main()
        {
            using (var container = new UnityContainer())
            {
                container.RegisterType<IB, B>(new TransientLifetimeManager());

                var b = container.Resolve<IB>();
                b.DoIt();
            }
        }
    }
}
Autofac would have handled this with expected behavior of disposable objects being disposed. It's ironic that Unity came from the Patterns and Practices group, and it was that group which had helped promote the idea of "least surprise" when designing objects and interfaces.
 
Back
Top Bottom