awesomium / awesomium-pub

9 stars 0 forks source link

Constant memory groth in WPF applications when continously creating and destroying WebControl instances (WPF, non-windowed) #49

Open mirabyte opened 10 years ago

mirabyte commented 10 years ago

continously creating and destroying WebControl instances (WPF, non-windowed, latest version) in a sample WPF application (.NET 4, x68), the memory used by the application increases rapidly and is not freed any more (even when using GC.Collect() and stuff like that).

Here is my code (creating the control): wbControl = new WebControl(); bdrContent.Child = wbControl; wbControl.Source = new Uri(@"http://www.some-url.com");

Here is my code (removing the control): wbControl.Stop(); wbControl.Dispose(); bdrContent.Child = null; wbControl = null;

Also directing the cache to the disk (instead of in-memory caching) does not help. The problem persists and is even worse if the the browser control is larger (so more real-estate of the Web page gets rendered). Also lots of Flash or other content on the Web page makes the problem worse. Seems to be an issue with the surface that gets rendered to the visual brush.

This memory groth affects the WPF application process - it does not affect the awesomium process which gets created and killed each time the control is recreated.

Perikles commented 10 years ago

This issue cannot be reproduced with the samples that come with the SDK, but these samples are tabbed, which means that the container (the tab, equivalent to your bdrContent), gets released too when a WebControl is released.

We need to investigate if there is something in the container (bdrContent), added or set either by you or us, that keeps the WebControl from being GC'ed. To do this, we require some addtional information such as:

Or if you have a simple WPF project that can reproduce this issue, it would be ideal.

mirabyte commented 10 years ago

Hi Perikles,

sure. I can send you a sample project (btw, also in the sample from the SDK this is reproducable!). I will upload it to skydrive and post the link here later on today.

Thanks for your help!

mirabyte commented 10 years ago

Hi again,

here is a simple sample project for you (VS2013, bin is included!):

http://1drv.ms/1iRUNk4

Just start the application and hit the "Start" button. A timer will then create a WebControl and load a sample page. After 5 sec. the control is destroyed by the timer. After another 5 sec. the control gets recreated and so on.

When you start the application, maximize the window to see the effect even better. There is a textbox showing the working memory set which is also updated every 5 sec. - just leave the app running for some minutes and you will see the constant increase. I also added a button to force the GC but as you can see this has no or only very little effect. After about one hour the memory consumption is really high and still keeps growing - it does not halt at some limit or so!

I used a memory profiler to see what is going on... there seems to be no leak in the manged components (the control seems to be removed fine) but there is a lot of native stuff on the heap which does not go away.

If you need any further information or if I can help you in nailing this down, please let me know!

mirabyte commented 10 years ago

Hi Perikles,

any news on this issue? Were you able to reproduce the issue with the sample application? Any feedback would be appreciated!

PassivePicasso commented 10 years ago

I wanted to chime in and say I am also experiencing memory issues that were not present with Awesomium 1.6, I have not created a test case yet to tease out the problem but once I do i'll add any findings here. From a high level it sounds very similar. We create a large number of browsers and dispose them on a regular basis. We also see memory growth with a browser that is left open and regularly has it's source changed. This browser could be left open for weeks previously without issue, now it generally crashes the entire .net application after a few hours due to the application running out of memory space.

PassivePicasso commented 10 years ago

The issues I encountered do not appear to be related to Awesomiums design. This may also be true for you as well mirabyte. I am unable to look at your code, but here are my findings. You may need to run additional disposal. If you have hooked up any GlobalJavaScriptObjects to EventHandlers, you may need to remove those manually, as well as Disposing the JavaScriptObject. Otherwise these will leave open connections to root Garbage Collection objects. You may also need to explicitly Dispose the webControl.WebSession object if you are setting one up.

Dealing with these kinds of issues allowed the GC to clean up nearly all of the Awesomium related components in my system, you might be running into similar issues. I was able to discover these problems using ANTS memory profiler. They have a 14 day free trial so you could look to see if you're having any of those problems using that tool.

mirabyte commented 10 years ago

Hi PassivePicasso,

thanks for your reply (at least you do reply! - the communication with the Awesomium developer team is really prolematic - which even may force us to look for alternatives...). You can download my sample from the link above. It uses none of the possible sources you mentioned in your post. It just creates a control, loads a Web site and then disposes the control again. The increase of memory is absolutely visible (if you use the Windowed mode, the problem is not there - only in the WPF native mode!). We also used a memory profiler (NET Memory Profiler by Scitech by the way) and according to our findings the memory is lost in the unmanged heap. I suspect a problem with the way the rendered images from the WebKit are moved over to the WPF world (via a bitmap buffer). If you try my sample from above, you can easily see the issue...

PassivePicasso commented 10 years ago

Mirabyte, you'll need to host that example elsewhere, or compose a comment in high detail with how to setup the files and project. Something like this would work: https://github.com/awesomium/awesomium-pub/issues/47

Otherwise find another hosting source, I can't get the file from where you've put it, I'm sure Perikles can't either.

mirabyte commented 10 years ago

Hi PassivePicasso,

I just checked this with Google Chrome and IE (also in private mode). Is is a public OneDrive folder and can be accessed. What kind of error do you get when you try to hit the link http://1drv.ms/1iRUNk4 ?

PassivePicasso commented 10 years ago

"Sorry, Something went wrong" Just copy and paste the pieces code into a comment and set the markup.

mirabyte commented 10 years ago

Here is the code, pretty simple....:

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // Prepare a 5 sec. timer for creating, loading and destroying the WebControl
            timer.Interval = TimeSpan.FromSeconds(5);
            timer.Tick += timer_Tick;
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            if (wbControl == null)
            {
                // Create the WebControl if it is not there and load a sample page
                wbControl = new WebControl();
                bdrContent.Child = wbControl;
                wbControl.Source = new Uri(@"http://www.spiegel.de");
            }
            else
            {
                // Remove the WebControl if is already there
                wbControl.Stop();
                wbControl.Dispose();
                bdrContent.Child = null;
                wbControl = null;
            }

            // Measure the used memory
            Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
            long totalBytesOfMemoryUsed = currentProcess.WorkingSet64;
            tbOutput.Text = (totalBytesOfMemoryUsed / 1048576).ToString();
        }

        private DispatcherTimer timer = new DispatcherTimer();
        private WebControl wbControl;

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            timer.Start();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            timer.Stop();
        }

        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
    }
PassivePicasso commented 10 years ago

I'll test this code as soon as I am able. However we reverted our software today, back to Awesomium 1.7.3, until the memory issues can be worked out. For perspective, in 1.7.3 we are able to spin up well over 200 awesomium processes and consume less than 1gb of memory in the parent process. In Awesomeium 1.7.4, we cannot spin up even 50 processes without causing the parent process to go over 1.3gb, causing it to crash due to a lack of memory availability due to the memory space limitation in 32bit applications.
I can easily swap 1.7.4 and 1.7.3 in our code base, all it requires is changing how we check the state of the WebCore. Simply changing 3 checks and replacing the libraries resolved all our issues with memory consumption and rampant crashing. Once I finish up the revert, I'm going to be looking at building a system to build off a new thread to run Awesomium and it's child processes, If I can resolve the memory issues with this progression I'll update this thread.

mirabyte commented 10 years ago

I finally managed to find the source of problem (at least I hope that this is the problem):

If I disable JavaScript in the WebPrefs or use a Website for testing that does not use any JavaScript AND if I use the following code to create/dispose the WebControl as well as the WebSession, the memory usage is more or less constant:

To create the control from code:

            session = WebCore.CreateWebSession(WebPreferences.Default);

            wbControl = new WebControl();
            wbControl.WebSession = session;
            bdrContent.Child = wbControl;  // Add to visual tree
            wbControl.Source = new Uri(@"http://www.spiegel.de/");

And to dispose:

            bdrContent.Child = null;  // remove from visual tree
            wbControl.Dispose();
            wbControl = null;

            session.Dispose();
            session = null;

As stated before, I have to set Awesomium.Core.WebPreferences.Default.Javascript = false; as well in order to prevent the control from leaking memory.

Perikles, here is my guess why: When you load a page you hook up to some events from the page (JavaScript) or the other way round. Disposing the WebControl does not dispose the WebSession nor it does detach the automatically created event handlers from the JavaScript code. That is why actually an entire WebView (incl. the surface object) remains in memory every time.

Disposing the WebSession manually is no problem (if one knows that this is needed), but the JavaScript issue cannot be adressed (deactivating JS is not the solution!). PLEASE, can you have a look at that? It should be easy to fix and would help to make the control really usable. Again, if you would like to get any help on memory profiling, feel free to contact me!

mirabyte commented 10 years ago

Update: The solution posted above is not 100% reliable. If JavaScript is enabled, then every time the Source URL is set on a newly created WebControl the native memory (not the managed!) peaks up and drops back to half of the peak. So what you get if you run this multiple times is constant increase of memory used. When JS is disabled, in MOST cases when Source URL is set the consumed native memory drops back after the peak to the previous level. But even in this case, sometimes you can see the same pattern as in the case with JavaScript enabled. And if memory usage goes up, it will never go down! So to sum it up: There is a heavy memory leak in WebControl (or its underlying classes) that becomes visible when creating and disposing the control from code. Actually, the dispose code from post above does not cause any (native) memory reduction as one would expect so it seems that it is not disposing the native bytes properly! BTW version 1.6.x had a similar problem but it was much smaller. Starting with version 1.7.x the problem is really heavy and an application can grow several 100 MBs after creating and disposing some WebControls. Again, the larger the control is, the heavier is the memory leak! You can also reproduce this with the tabbed WPF browser sample that is included with Awesomium, so it is not specific to custom code!

PassivePicasso commented 10 years ago

Perikles, has anything been done on this? We really need the benefits provided in 1.7.4, however we cannot move forward until memory issues have been dealt with.

cgdietz commented 10 years ago

I just wanted to chime in here and say we are seeing much higher memory on 1.7.4 vs 1.7.3 as well. We launch as many as 30 of the browsers at a time from our parent process and there is an order of magnitude difference in the memory use between these versions.

mirabyte commented 9 years ago

I'm sorry to let you know that the issue still exists even in the latest version 1.7.5! Any feedback regarding this issue would be appreciated! Without finding a solution for this memory leak, the control is virtually not usable. :-(