locked
WCF Service Memory Leak RRS feed

  • Question

  • Dear All !

    I have got a problem: it seems that Silverlight has a serious memory leak and I need your advice. I have a simple Silverlight4 app that must call WCF service let's say once a second and request sync. info ( in my case it's a simple structure but here I gave a similar example ), read data, handle it call service again and so on. Actually I'm really confused cause it must be a very widely spread scenario but I failed to find anything which could help: memory leak has been mentioned a few times but nothing specific or exact. Guys I need help - I'm abould to deliver solution and this bug still exists - with this bug my app is a garbage.

    Here is the simple scenario which consumes memory indefinetely. I create simple Silv app, added class and created the instance. As you can see timer starts up at the constructor. I removed all try/catch/lock/everything for simplicity:

    #region Usings
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Browser;
    using System.Xml;
    using System.Text;
    using System.IO;
    using System.Diagnostics;
    using System.ServiceModel;
    using System.ComponentModel;
    using System.Collections;
    using System.Collections.Generic;
    using System.Threading;
    using System.Windows.Threading;

    using MemLeakTest.SyncService;
    #endregion

    namespace MemLeakTest
    {
        public class ExternalServiceConsumer
        {
            #region Class members
            private DispatcherTimer m_timer;
            private TestServiceClient m_serviceSync;
            #endregion

            #region Class initialization
            public ExternalServiceConsumer()
            {
                m_timer = new DispatcherTimer();
                m_timer.Tick += new EventHandler( OnSyncTimerElapsed );
                m_timer.Interval = TimeSpan.FromSeconds( 0.1 );

                SetupSyncService();

                m_timer.Start();
            }
            protected void SetupSyncService()
            {
                EndpointAddress address = new EndpointAddress( "../TestService.svc" );

                m_serviceSync = new TestServiceClient(
                    new BasicHttpBinding(), address );

                m_serviceSync.SyncCompleted +=
                    new EventHandler<SyncCompletedEventArgs>( OnSyncCompleted );
            }
            #endregion

            #region Class 'Sync' methods
            protected void OnSyncTimerElapsed( object sender, EventArgs ea )
            {
                string sRequest = new string( 'b', 1024 * 8 );
                m_serviceSync.SyncAsync( sRequest );
            }
            protected void OnSyncCompleted( object sender, SyncCompletedEventArgs ea )
            {
                string sRes = ea.Result;

                //Debug.WriteLine( sRes );
                //Debug.WriteLine( "---" );
            }
            #endregion

        


        }
    }


    and this is service:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    
    namespace MemLeakTest.Web
    {
    	// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "TestService" in code, svc and config file together.
    	public class TestService:ITestService
    	{
    		public string Sync( string sIncomingData )
    		{
    			return sIncomingData;
    		}
    	}
    }
    


    PS: i tried to recreate service client instance before every request - but it does not help


    Guys relly need help

    Tuesday, October 19, 2010 12:48 AM

Answers

  • So, you're saying that everything works fine on Windows 7, but the bug appears on Windows XP with both IE7 and IE8? Maybe you should file a bug report on connect then.

    Wednesday, October 20, 2010 2:48 AM

All replies

  • Hi. Can you elaborate a bit on what makes you think this is leaking memory? I cannot see a leak.

    What I do see however is that you're firing services calls without checking whether your client is able to keep up with it. You're sending 10 requests per second; if either your service or your client is not able to process all of them, they'll pile up and you can effectively stall your application. You can add a counter to that sample class and increase it when you send a request, decrease it when you receive the completed event. Depending on your system you might see that you get an increasing number of concurrent (pending) requests. Since you're sending several KiB of data with each request, which results in a total required upload bandwidth in the ~100 KiB/s, you'll probably see this happen very quickly when you try to use something like that over a normal DSL/cable internet connection (unless you have a really fast upload).

    A safer pattern maybe would be something like:

    protected void OnSyncTimerElapsed(object sender, EventArgs ea)
    {
        m_timer.Stop();
        string sRequest = new string('b', 1024 * 8);
        m_serviceSync.SyncAsync(sRequest);
    }


    And then:

    protected void OnSyncCompleted(object sender, SyncCompletedEventArgs ea)
    {
        string sRes = ea.Result;
                
        //Debug.WriteLine( sRes );
        //Debug.WriteLine( "---" );
    
        m_timer.Start();
    }



    Tuesday, October 19, 2010 2:05 AM
  • The point is that I do it exactly the way you showed - my example was kind of raw model:

    This is my code:

    protected void OnSyncTimerElapsed( object sender, EventArgs ea )
            {
                m_timer.Stop();

                lock( m_syncObject )
                {
                    if( !m_bRun )
                        return;

                    SyncRequest request = GenerateSyncRequest();

                    try
                    {
                        m_serviceSync.ProcessSyncRequestAsync( request );
                    }
                    catch
                    {
                    }
                    finally
                    {
                    }
                }
            }


    protected void OnProcessSyncRequesCompleted( object sender,
                ProcessSyncRequestCompletedEventArgs ea )
            {
                lock( m_syncObject )
                {
                    try
                    {
                        SyncResponse response = ea.Result;

                        if( null != response )
                        {
                            HandleSyncResponse( response );
                        }

                    }
                    catch
                    {
                        ProductionLine.ShowSyncServiceIndicator( false );
                    }
                    finally
                    {
                        RechargeTimer();
                    }
                }
            }


    But is does not matter: I start up application at my machine and takes IE about 20 mins to engulf 100 mb. Every time i leave a stress test ove the night I face an exception. Could you try to launch the sample. Something is wrong and I can't understand what exactly.


    Tuesday, October 19, 2010 2:57 AM
  • BTW: I use 4 request per second 10k each.

    As for me it's tiny request ~40k per/sec

    Tuesday, October 19, 2010 3:09 AM
  • Hi again. I've built a small example using the code from your first post. I added the timer.Stop() and Start() calls I mentioned, and to get a better idea of the memory usage, I've forced the garbage collector to clean up after each call.

    The result is that the sample is now running for 30 minutes straight, and memory usage of the IE process currently is at 30224 KByte. It started at 30780 KByte.

    It must be something else on your side. With the sample, I don't get any memory leaks or crashes.

    Please note that I'm currently using IE9 beta.

    Tuesday, October 19, 2010 5:55 AM
  • Can you show me the example how you're using GarbageCollector.

    I'm using ie7. But with FF it looks the same. If it does not help I'll attach a sample project - maybe you'll be so kind to start it up  and play around for awhile. Really I havn't been so perplexed for a long time.

    Tuesday, October 19, 2010 6:33 AM
  • Here is the code I'm using. I just realized it's still running. it's now been way more than an hour and still no problems; 30332 KBytes.


    public class ExternalServiceConsumer
    {
        #region Class members
    
        private DispatcherTimer m_timer;
        private TestServiceClient m_serviceSync;
    
        private int _concurrentRequests = 0;
    
        #endregion
    
        #region Class initialization
    
        public ExternalServiceConsumer()
        {
            m_timer = new DispatcherTimer();
            m_timer.Tick += new EventHandler(OnSyncTimerElapsed);
            m_timer.Interval = TimeSpan.FromSeconds(0.01);
    
            SetupSyncService();
    
            m_timer.Start();
        }
    
        protected void SetupSyncService()
        {
            m_serviceSync = new TestServiceClient();
    
            m_serviceSync.SyncCompleted +=
                new EventHandler<SyncCompletedEventArgs>(OnSyncCompleted);                        
        }
    
        #endregion
    
        #region Class 'Sync' methods
    
        protected void OnSyncTimerElapsed(object sender, EventArgs ea)
        {
            m_timer.Stop();
    
            string sRequest = new string('b', 1024 * 8);
    
            try
            {
                _concurrentRequests++;
    
                if (_concurrentRequests > 1)
                {
                    Debug.WriteLine("Concurrent requests: " + _concurrentRequests);
                }
    
                m_serviceSync.SyncAsync(sRequest);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
        }
    
        protected void OnSyncCompleted(object sender, SyncCompletedEventArgs ea)
        {
            try
            {
                _concurrentRequests--;
                if (ea.Error != null)
                {
                    Debug.WriteLine("Error property is not null: " + ea.Error.ToString());
                }
                else
                {
                    // just do something with the result
                    string sRes = ea.Result;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
                
            GC.Collect();
            GC.WaitForPendingFinalizers();            
    
            m_timer.Start();
        }
    
        #endregion        
    }



    The service code is the same as yours, just bounces the argument.

    I've put that in a new Silverlight project, and all I do in addition is create a new instance of ExternalServiceConsumer in the Loaded event of the main page and store the reference in a class field of that page.


    Tuesday, October 19, 2010 6:45 AM
  • Hi MisterGoodcat !

    I tried to play arond and what ha beed discovered. Firstly I wanna mention that I work in WinXp+IE7.

    The first step i tried  to test everythin in Win7+IE8 - and it turned out to be pefrect - everything was as expected (both sample and real app). Now I installed IE8 - but it still sucks. Of course Win7+IE8 is a solution and that means that I'll 'survive' but I'm just curious it is a real bug - how can Microsoft coment it. I'm still wondering cause it must be very useful scenario. Or does everybody use Win7 ?

    Wednesday, October 20, 2010 2:10 AM
  • So, you're saying that everything works fine on Windows 7, but the bug appears on Windows XP with both IE7 and IE8? Maybe you should file a bug report on connect then.

    Wednesday, October 20, 2010 2:48 AM
  • Hi,

    You need to change the architecture of your approach. Try to use You silverlight Duplex polling. By that way the server can communicate with its client andpush data if there is any updates on the sever DB.

    This is a very good and suggessted approach. Refer the links below.

    http://weblogs.asp.net/dwahlin/archive/2008/06/19/pushing-data-to-a-silverlight-client-with-a-wcf-duplex-service-part-ii.aspx

    www.silverlightshow.net/.../The-duplex-story-looking-at-duplex-communication-in-Silverlight-4-Part-2-Using-Sockets.aspx

    http://msdn.microsoft.com/en-us/library/cc645027(VS.95).aspx

    Thanks,

    Thani

     

    Wednesday, October 20, 2010 5:23 AM
  • Duplex polling is just a workaround which does not guarantee anything. BTW: the first thing I ran into when looking up this bug was a similar discussion regarding memleak exactly in pooling.

    Gyus ! It's a god damn simple scenario - one request per sec. It must work like a clock. It works in Win7+IE8+Silv4  but it sucks in WinXP+IE7/8+Silv4. Can Microsoft explain it ?

    Wednesday, October 20, 2010 6:04 AM