Page 1 of 2 12 LastLast
Results 1 to 10 of 15

Thread: Memory leak in REST store , extjs v4 and v5

  1. #1
    Sencha User
    Join Date
    Jan 2014
    Posts
    11

    Default Memory leak in REST store , extjs v4 and v5

    Hi,

    Can anyone throw some light on a very strange issue I have with both v4 and v5 of EXT JS.

    I've been struggling with this for some time using v4 and today I tried v5 with the same results. I want to create a SPA which will dynamically update data every second and run continually without crashing for weeks if need be. So far I have failed to get the stability I need. So I started creating simple test apps to see where the problems are and it seems to be something to do with the REST store I am using, but it's not clear exactly what is wrong.

    I create an app with sencha cmd, I add a singleton with some global variables and functions, including a dynamicTask with a 1 second interval, which I start running in the app launch function. The dynamicTask just creates a REST store and loads it, nothing else.

    I have a datasnap app running on port 81 on my server pc servicing the requests.

    If I run the EXT app in a browser on the server pc it works fine, going for days, even weeks at a time, without issue, but, if I run the same EXT app in a browser on a remote pc, in just one or two days the browser has crashed due to a slow memory leak in the EXT app.

    Why there is a different result when the app is local or remote to the REST server, I don't know, but it definitely behaves in this way, I have run this test, or something like it, hundreds of times over the last 2 weeks.

    Has anyone else experienced anything like this? I am at a complete loss to explain it. I guess it must be my code but I've stripped almost everything out and it still happens.

    I'm using windows 7 professional SP1 / firefox 27.0.

    Code follows :

    RT.globals dynamic task

    Code:
    dynamicTask: {
            run: function() {
                var store                   = Ext.create( 'RT.store.dynamic.Stats', {});
                store.load();
                store                         = null;
            },
            interval: 1000
        }

    Application launch function

    Code:
    launch: function () {
            // TODO - Launch the application
            Ext.TaskManager.start(RT.globals.dynamicTask);
        }
    The store definition is

    Code:
    Ext.define('RT.store.dynamic.Stats',{
        extend:                     'Ext.data.Store',
        requires: [
                                        'RT.globals'
        ],
        model:                      'RT.model.dynamic.Atom',
        storeId:                    'dynamicStats',
        autoLoad:                           false,
        clearRemovedOnLoad:         true,
        clearOnPageLoad:               true,
    
        Proxy: {
                type:                   'rest',
                url :                     '/datasnap/rest/TCcs400Server/dynamicAll//',
                reader: {
                    type:               'json',
                    rootProperty:    ''
                }
         },
         listeners: {
             beforeLoad: function(store, operation, eOpts) {
                  store.proxy.url        = '/datasnap/rest/TCcs400Server/dynamicAll/' + RT.globals.license  + '/';
    
             },
             load : function(store, records, successful, eOpts) {
             }
        }
    });
    And the model is

    Code:
    Ext.define('RT.model.dynamic.Atom',{
        extend: 'Ext.data.Model',
            requires: [
                'RT.globals'
            ],
        fields: [
                {name:  'id'},
                {name: 'AtomId'}, 
                {name: 'CallsInQueue'}, 
                {name: 'AgentsInReady'}, 
                {name: 'AgentsInRing'}, 
                {name: 'AgentsInTalk'}, 
                {name: 'AgentsInWrapUp'}, 
                {name: 'AgentsOccupied'},
                {name: 'AgentsPaused'},
                {name: 'AgentsNonAcdIn'},
                {name: 'AgentsNonAcdOut'},
                
                {name: 'LongestInQueue'},
                {name: 'LongestWaitToAnswer'},
                
                {name: 'AvgQueue'},
                {name: 'AvgRing'},
                {name: 'AvgTalk'},
                {name: 'AvgWrapUp'},
                {name: 'AvgReady'},
                {name: 'AvgOccupied'},
                {name: 'AvgPause'},
                
                {name: 'Offered'},
                {name: 'Answered'},
                {name: 'AvgAnswer'},
                {name: 'AnsweredInSL'},
                {name: 'Abandoned'},
                {name: 'AvgAbandon'},
                {name: 'AbandonedInSL'},
                
                {name: 'TransferredIn'},
                {name: 'TransferredOut'},
                
                {name: 'Rejected'},
                {name: 'Deflected'},
                {name: 'OverflowedIn'},
                {name: 'OverflowedOut'},
                {name: 'PickedUp'},
                {name: 'PassedToNonACD'},
                {name: 'OvfOutBusyAgents'},
                {name: 'OvfOutNoAgents'},
                {name: 'OvfOutMaxWait'},
                
                {name: 'LongestRing'},
                {name: 'LongestTalk'},
                {name: 'LongestClerical'},
                {name: 'LongestAbandon'},
                {name: 'NonAcdIn'},
                {name: 'AvgNonAcdIn'},
                {name: 'NonAcdOut'},
                {name: 'AvgNonAcdOut'},
                
                {name: 'AtomLongName'},
                
                {name: 'BusyAgents'},
                {name: 'TotalAgents'},
                
                {name: 'SLTcolour'},
                {name: 'CWTcolour'},
                {name: 'ALOcolour'},
                {name: 'CIQcolour'},
    
                {name: 'LostCalls'},
                
                {name: 'license'},
                {name: 'error'}
            ]
    });

  2. #2
    Sencha Premium Member skirtle's Avatar
    Join Date
    Oct 2010
    Location
    UK
    Posts
    3,791
    Answers
    585

    Default

    A heap dump might give you a hint as to exactly what is leaking.

    This line achieves nothing:

    Code:
    store                         = null;
    Better is:

    Code:
    Ext.destroy(store);
    If the leak is somewhere between the store and models then destroying it should help.

    You also have 'Proxy' with a capital letter in your config. That won't be causing the leak but it needs fixing.

  3. #3
    Sencha User
    Join Date
    Jan 2014
    Posts
    11

    Default

    Hi skirtle,

    Thanks for taking the trouble to reply.

    I set store to null to avoid any inner/outer reference issues. As there is no other reference to the store in the code, when the load completes I assume the store and all it's associated memory/events/listeners/callbacks/etc should be deleted by the js garbage collector.

    I don't use Ext.destroy(store) because the load() is asynchronous and there's no guarantee it has completed in the dynamicTask function. I didn't think Ext.destroy would cope with that.

    I did try putting
    Code:
    store.removeAll();
    in the store load listener, but it made no difference, nor did
    Code:
    store.destoryStore();
    which is deprecated in v5 but not in v4, I think.

    I am looking at using Chrome's heap profiler to try and track it down, just wondered if anyone had seen anything similar and could point me in the right direction. Still can't understand why I get no leak when it's run in the browser on the same pc as the datasnap server, what's the difference in this instance?

    The P is a typo, it's 'proxy' in the actual code. Sorry about that.

  4. #4
    Ext JS Premium Member
    Join Date
    Nov 2008
    Posts
    107
    Answers
    1

    Default

    Hi grdrew,

    i think this is completely wrong if you set store to null, after load() operation, since load is assinchronous.

    Maybe it would be better to use gloabal store variable (instance) and just load it in task?


    Code:
    var global_store  = Ext.create( 'RT.store.dynamic.Stats', {});
    dynamicTask: { 
      run: function() {
            global_store.load();
      },        
      interval: 1000    
    }
    Or you can create store with storeId and than in run task you reference it like this: Ext.data.StoreManager.lookup('myStoreId').load()

    best regards.

  5. #5
    Sencha Premium Member skirtle's Avatar
    Join Date
    Oct 2010
    Location
    UK
    Posts
    3,791
    Answers
    585

    Default

    andrej_marincic makes a good point, if you reload the same store it greatly reduces your exposure to potential leaks.

    Ext.destroy won't cope with the asynchronous store load so you'd have to wait until you're done with the store. In Ext 4 it just calls destroyStore but in 5 that's been renamed to destroy. Using Ext.destroy will work in either case. Destroying the store will go much further than a removeAll.

    You're using a storeId, so the store will be registered with the StoreManager. That will stop it being garbage collected. However, as far as I can tell creating another store with the same id should remove the original store from the store manager.

    A quick diff with the heap profiler should get you your answer pretty quickly.

  6. #6
    Sencha User
    Join Date
    Jan 2014
    Posts
    11

    Default

    Hi skirtle and andrej_marincic

    The code I've posted here is the nth iteration of very many variations. I've been after this for some time. Initially I had a global variable, as you suggest, and just did a reload() in the dynamic task, with further processing in the store load listener. I assumed the leak was in my code so I started stripping everything out until finally I just created a new app and added the basic elements you see in the code.

    You're using a storeId, so the store will be registered with the StoreManager. That will stop it being garbage collected. However, as far as I can tell creating another store with the same id should remove the original store from the store manager.
    That's interesting, I added the storeId so that I could use StoreManager to reference it, but in a previous iteration I have used StoreManager.getCount() in a console log every time I drop into the dynamic task to show that there are no stores registered, which means the store is being deleted even with the storeId set.

    I'm not expecting an answer to this, it's dug in too deep for that, but I'm thinking that the fact this only leaks when the browser is remote must be important, when it's local to the datasnap web server there is no leak.

    Is there anyone with a good understanding of the XMLHttpRequest request/response cycle who can give me some idea where to look. There must be some difference when the browser is remote to the web server.

  7. #7
    Ext JS Premium Member
    Join Date
    Nov 2008
    Posts
    107
    Answers
    1

    Default

    hi grdrew,

    difference between remote and local server is latency Maybe the problem is in setting store to null just after load call. Maybe in local server version, load call finishes earlier, and when you set store to null, you do not make much "damage" but if you are using remote server, where latency is higher, setting store to null creates memory leaks.

    What if you do not set store = null or if you make some sort of cleanup in load callback?

    store.load({
    callback: function(){//make some sort of cleanup here for instance}
    })

    best regards,
    andrej

  8. #8
    Ext JS Premium Member
    Join Date
    Nov 2008
    Posts
    107
    Answers
    1

    Default

    One more idea, what if some load() calls fail, and you do not handle it correctly?

    You can log successfull and unsuccessfull load calls like this:

    Code:
    store.load({ 
       scope:this, 
       callback:function(records, operation, success){
           console.log("success:", success);
        }
    });
    

  9. #9
    Sencha Premium Member skirtle's Avatar
    Join Date
    Oct 2010
    Location
    UK
    Posts
    3,791
    Answers
    585

    Default

    Does the problem happen across all browsers or is it just Firefox?

  10. #10
    Sencha User
    Join Date
    Jan 2014
    Posts
    11

    Default

    Hi andrej

    Of course you are right, time is the difference! I will kick myself if it's a simple timing issue

    I did have an iteration of the app when I added store.destoryStore() to the store load listener, and that didn't make a difference, but that also included store = null in the dynamic task function.

    I can't make any changes until Monday but I'll try removing the store = null from the dynamic task and adding store.destroy() to the load listener to see if this stops the leak.

    One more idea, what if some load() calls fail, and you do not handle it correctly?
    I'm pretty sure this is not an issue, I have a closed intranet setup, server pc and remote pc, with no unexpected activity. I did originally have full error handling in the store load listener but I have stripped it out to eliminate it as a possible cause of the leak. There was never one time though, when the request/response actually failed.

    skirtle

    Does the problem happen across all browsers or is it just Firefox?
    So far I've seen it in firefox and in chrome. I have not got round to looking at it in explorer yet.

    A quick diff with the heap profiler should get you your answer pretty quickly.
    I did try using chrome's profiler, and there were some detached DOM tree items in the heap snapshots I took, but I don't have enough experience to make any real sense of what I was seeing so I'll have to read the doc's and give it another go when I get back in on Monday.

Page 1 of 2 12 LastLast

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •