0

In our application, we are using PngBitmapEncoder to encode and save PNG image in a separate thread\task. After few days of running the application we are seeing Dispatcher cannot be created from Encoder and throws error

Not enough storage is available to process the command

And has the below call stack

System.ComponentModel.Win32Exception (0x80004005): Not enough storage is available to process this command
   at MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle, Int32 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHook[] hooks)
   at System.Windows.Threading.Dispatcher..ctor()
   at System.Windows.Threading.DispatcherObject..ctor()
   at System.Windows.Media.Imaging.BitmapEncoder..ctor(Boolean isBuiltIn)

As .Net is available open source, got curious of what line inside Dispatcher constructor is throwing the error

    [SecurityCritical, SecurityTreatAsSafe]
    private Dispatcher()
    {
        _queue = new PriorityQueue<DispatcherOperation>();

        _tlsDispatcher = this; // use TLS for ownership only
        _dispatcherThread = Thread.CurrentThread;

        // Add ourselves to the map of dispatchers to threads.
        lock(_globalLock)
        {
            _dispatchers.Add(new WeakReference(this));
        }

        _unhandledExceptionEventArgs = new DispatcherUnhandledExceptionEventArgs(this);
        _exceptionFilterEventArgs = new DispatcherUnhandledExceptionFilterEventArgs(this);

        _defaultDispatcherSynchronizationContext = new DispatcherSynchronizationContext(this);

        // Create the message-only window we use to receive messages
        // that tell us to process the queue.
        MessageOnlyHwndWrapper window = new MessageOnlyHwndWrapper();
        _window = new SecurityCriticalData<MessageOnlyHwndWrapper>( window );

        _hook = new HwndWrapperHook(WndProcHook);
        _window.Value.AddHook(_hook);

        // DDVSO:447590
        // Verify that the accessibility switches are set prior to any major UI code running.
        AccessibilitySwitches.VerifySwitches(this);
    }

Update

Updated the constructor code from .net open source. The dispatcher.cs is available here https://referencesource.microsoft.com/#WindowsBase/Base/System/Windows/Threading/Dispatcher.cs,078d6b27d9837a35

After some investigation we found that issue occurs after some 15000 iterations(each iteration creates a new thread and calls PngBitmapEncoder). Then found that this is linked to Global Atom Table limit (0x4000 or 16384). More details on Global Atom Table here https://learn.microsoft.com/en-us/archive/blogs/ntdebugging/identifying-global-atom-table-leaks

The dispatcher created each time makes an entry in the Global atom table and on thread exit this entry is not cleared. This leads to leak in Global atom table and when it reaches the max limit, it throws "Not enough storage...." error. This seems like a issue with Microsoft's handling of Dispatcher. Even the PngBitmapEncoder documentation, I do not see any remark with respect to Dispatcher handling and any explicit shutdown of dispatcher.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Rahul Sundar
  • 480
  • 8
  • 26
  • 2
    What you've shown is the *static* constructor, which would show as `.cctor`. That isn't the *instance* constructor, which is what's throwing. – Jon Skeet Aug 16 '20 at 13:52
  • It seems to be a memoryleak or? This is unmanaged code and you have to free used memory, are you shure doing this? – RoXTar Aug 16 '20 at 13:55
  • Dispatchers are created one-per-thread, and creates a native window for receiving Windows messages. They're stored in a weak reference so they'll get finalized/disposed when you no longer reference them. You must be holding onto the DispatcherObject somehow (which a BitmapEncoder is a DispatcherObject) so they never release the Dispatcher even after the thread dies. – Bryce Wagner Aug 16 '20 at 14:10
  • 1
    Wrong class, wrong constructor, it is HwndWrapper that fails. You can probably diagnose this issue with Task Manager, add the "User Objects" column. The show is over when it reaches 10000 for your process, 65535 for all processes combined that run on the same desktop. If this is a service then much lower limits apply. – Hans Passant Aug 16 '20 at 14:19
  • @JonSkeet Good observation. The code from .Net open source is from .Net 4.8, whereas the call stack is from .Net 4.5.1. I thought the code wouldn't have changed fro this constructor from version 4.5.1 to 4.8. From your observation, I am probably looking at wrong version of code. – Rahul Sundar Aug 16 '20 at 17:00
  • @HansPassant Let me check the UserObjects column. By the way we are using Windows Server 2008 R2. So is the limit 10000 same here too? – Rahul Sundar Aug 16 '20 at 17:01
  • 1
    @RoXTar We did not observe noticeable memory leak trend over a period of time (3 days) and neither handle leak. This what made me look mysterious on the problem – Rahul Sundar Aug 16 '20 at 17:02

1 Answers1

1

I had this issue years ago when doing background processing using objects in the System.Windows.Media.Imaging namespace too. I came across a blog entry from a Microsoft engineer admitting that it was an issue, but that there wasn't enough interest to resolve it or something to that effect. I remember hoping they'd fix it in revisions to the framework. The engineer posted a solution that works for me.

I should mention that I've tried using the solution within the thread pool by using System.Threading.ThreadPool.QueueUserWorkItem() or System.Threading.Tasks.Task.Run(), but have found the solution does not work in the thread pool; perhaps because the thread is reused. The only way that I have been able to resolve the issue is by using a System.Threading.Thread to do the work. Below is the basic idea on how to get around the issue and force the resource to release.

new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
    // Do some imaging work.

    // This asks the dispatcher associated with this thread to shut down right away.
    System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Normal);
    System.Windows.Threading.Dispatcher.Run();
})).Start();
Rich
  • 261
  • 1
  • 7
  • Consoling to see that I am not alone experienced this issue. With respect to the solution , I have 2 questions. Q1) Can I use InvokeShutdown() to synchronously call it. What is the cost in InvokeShutdown(), will it take more time or block something for a while? Q2) Why do we need to Run the dispatcher again after shutting down? – Rahul Sundar Aug 28 '20 at 10:56
  • 1
    The entire thing is disappointing that they haven't fixed it yet, I had this issue years ago. The solution seems like a hack to me because I have the same questions as you. I suppose you could try InvokeShutdown(), but if your process is like mine it would take days to determine if the change works. Those two lines of code that the engineer blogged about definitely fix the problem as-is. Start with those lines and verify it resolves your issue. Then if you want to tinker with those lines and have good results, let me know. Like I said I tinkered with the thread pool and it didn't work there. – Rich Aug 28 '20 at 13:32
  • Thanks for sharing your results. Both BeginInvokeShutdown() and InvokeShutdown() works for me. Let me beat this down by running for weeks and will update if any further interesting results. Thanks again – Rahul Sundar Aug 29 '20 at 06:30