0

I have a console application as my webjob to process notifications inside my application. The processes are triggered using a queue. The application interacts with a SQL Azure database using entity framework 6. The Process() method that's being called reads/write data to the database.

I'm getting several errors when the queue messages are processed. They never get to the poison queue since they are reprocessed successfully after 2-3 times. Mainly the errors are the following:

An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll

Error: System.OutOfMemoryException: Exception of type ‘System.OutOfMemoryException’ was thrown.

The default batch size is 16, so the messages are processed in parallel.

My guess is that the Ninject setup for processing messages in parallel is wrong. Therefore, when they are processed at the same time, there are some errors and eventually they are processed successfully.

My question is: Does this setup look ok? Should I use InThreadScope() maybe since I don't know the parallel processing is also multi-threaded.

Here's the code for my application.

Program.cs


namespace NotificationsProcessor
{
    public class Program
    {
        private static StandardKernel _kernel;
        private static void Main(string[] args)
        {
            var module = new CustomModule();
            var kernel = new StandardKernel(module);
            _kernel = kernel;

            var config =
                new JobHostConfiguration(AzureStorageAccount.ConnectionString)
                {
                    NameResolver = new QueueNameResolver()
                };
            var host = new JobHost(config);
            //config.Queues.BatchSize = 1; //Process messages in parallel
            host.RunAndBlock();
        }

        public static void ProcessNotification([QueueTrigger("%notificationsQueueKey%")] string item)
        {
            var n = _kernel.Get&ltINotifications>();
            n.Process(item);
        }

        public static void ProcessPoison([QueueTrigger("%notificationsQueueKeyPoison%")] string item)
        {
            //Process poison message. 
        }
    }
}

Here's the code for Ninject's CustomModule


namespace NotificationsProcessor.NinjectFiles
{
    public class CustomModule : NinjectModule
    {
        public override void Load()
        {
            Bind&ltIDbContext>().To&ltDataContext>(); //EF datacontext
            Bind&ltINotifications>().To&ltNotificationsService>();
            Bind&ltIEmails>().To&ltEmailsService>();
            Bind&ltISms>().ToSmsService>();
        }
    }
}

Code for process method.


    public void ProcessMessage(string message)
    {
        try
        {
            var notificationQueueMessage = JsonConvert.DeserializeObject&ltNotificationQueueMessage>(message);
            //Grab message and check if it has to be processed
            var notification = _context.Set().Find(notificationQueueMessage.NotificationId);

            if (notification != null)
            {
                if (notification.NotificationType == NotificationType.AppointmentReminder.ToString())
                {
                    notificationSuccess = SendAppointmentReminderEmail(notification); //Code that sends email using the SendGrid Api
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex + Environment.NewLine + message, LogSources.EmailsService);
            throw;
        }                   
    }


Update - Added Exception

The exception is being thrown at the Json serializer. Here's the stack trace:

Error: System.OutOfMemoryException: Exception of type ‘System.OutOfMemoryException’ was thrown. at System.String.CtorCharCount(Char c, Int32 count) at Newtonsoft.Json.JsonTextWriter.WriteIndent() at Newtonsoft.Json.JsonWriter.AutoCompleteClose(JsonContainerType type) at Newtonsoft.Json.JsonWriter.WriteEndObject() at Newtonsoft.Json.JsonWriter.WriteEnd(JsonContainerType type) at Newtonsoft.Json.JsonWriter.WriteEnd() at Newtonsoft.Json.JsonWriter.AutoCompleteAll() at Newtonsoft.Json.JsonTextWriter.Close() at Newtonsoft.Json.JsonWriter.System.IDisposable.Dispose() at Newtonsoft.Json.JsonConvert.SerializeObjectInternal(Object value, Type type, JsonSerializer jsonSerializer) at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Type type, Formatting formatting, JsonSerializerSettings settings) at Core.Services.Communications.EmailsService.SendAppointmentReminderEmail(Notificaciones email) in c:\ProjectsGreenLight\EAS\EAS\EAS\Core\Services\Communications\EmailsService.cs:line 489 at Core.Services.Communications.EmailsService.ProcessMessage(String message) in c:\ProjectsGreenLight\EAS\EAS\EAS\Core\Services\Communications\EmailsService.cs:line 124 at Core.Services.NotificacionesService.Process(String message) in c:\ProjectsGreenLight\EAS\EAS\EAS\Core\Services\NotificacionesService.cs:line 56
lopezbertoni
  • 3,551
  • 3
  • 37
  • 53
  • Without the code in `INotifications.Process` it is hard to tell what's happening there - there is nothing obviously wrong in what you posted. Inspect the code in `Process` to see if there is an infinite recursion or something that can cause an out of memory. – Victor Hurdugaci Oct 21 '14 at 00:07
  • I updated the question with the method. What's weird is that after retrying the message goes through in 2-3 tries. That's why I'm thinking that maybe the Ninject configuration is causing trouble. – lopezbertoni Oct 21 '14 at 00:26
  • 1
    Related: https://stackoverflow.com/a/14592419/264697 – Steven Oct 21 '14 at 17:08

1 Answers1

1

Since you're receiving OutOfMemoryExceptions and StackOverflowExceptions, i suggest that there may be a recursive or deeply nested method. It would be extremely helpful if you'd have a stacktrace to the exception, sadly that's not the case for StackOverflowExceptions. However, OutOfMemoryException has a stack trace, so you need to log this and see what it says / add it to your question. Also, as far as the StackoverflowException goes, you can also try this.

Scoping

You should not use .InThreadScope() in such a scenario. Why? usually the thread-pool is used. Thread's are reused. That means, that scoped objects live longer than for the processing of a single messages. Currently you are using .InTransientScope() (that's the default if you don't specify anything else). Since you are using the IDbContext in only one place, that's ok. If you'd wanted to share the same instance across multiple objects, then you'd have to use a scope or pass along the instance manually.

Now what may be problematic in your case is that you may be creating a lot of new IDbContexts but not disposing of them after you've used them. Depending on other factors this may result in the garbage collector taking longer to clean-up memory. See Do i have to call dispose on DbContext.

However i grant that this won't fix your issue. It might just speed up your application. Here's how you can do it anyway:

What you should do is: Have INotifications derive from IDisposable:

public interface INotifications : IDisposable 
{
    (...)
}

internal class Notifications : INotifications
{
    private readonly IDbContext _context;

    public Notifications(IDbContext context)
    {
        _context = context;
    }

    (...)

    public void Dispose()
    {
        _context.Dispose();
    }
}

and alter your calling code to dispose of INotifications:

    public static void ProcessNotification([QueueTrigger("%notificationsQueueKey%")] string item)
    {
        using(var n = _kernel.Get<INotifications>())
        {
           n.Process(item);
        }
    }
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • I've generally thought it to be a last resort to include references to my DI library of choice outside of the composition root (the _kernel reference above). I've been in similar situations before and my solution was to inject a resource factory. The using() block would still apply but there would be no reference to the container. Thoughts? – Jeremy F Oct 30 '14 at 18:36
  • @Jeremy F Instead of `IResolutionRoot.Get` you could inject a `Func` which is supported by at least Ninject (requries ninject.extensions.factory), Unity and AutoFac.. and i think several more. However i myself don't like the `Func<..>` so much and i usually rely on factories auto generated from interfaces (ninject `.ToFactory()` binding). I've also implemented this for Unity and i think implementing it for quite a lot of others would be possible, too. – BatteryBackupUnit Oct 31 '14 at 07:50
  • addendum: if all you'd do is create some factory implementation which itself uses `IResolutionRoot` you would not eliminate the reference to the container, you'd only hide it. This could be somewhat beneficial in case you could use only one (or a few) generic factories and as such lessen the ammount of direct container dependencies = less work to change when switching containers. However, there are those who say that generic factory interfaces violate the Interface Segregation Principle (ISP)... – BatteryBackupUnit Oct 31 '14 at 07:52
  • Yep I love the Ninject Factory Extensions as well. I haven't come across ISP issues, I will have to do some research. Cheers – Jeremy F Nov 01 '14 at 18:50