0

SCENARIO

I'm using the windows gadget platform with this gadget:

http://win7gadgets.com/pc-system/sushis_driveinfo.html

PROBLEM

The gadget has a memory leak, If I keep running this gadget +24h. it can increase the RAM consumption up to 1 GB, while other similar gadgets does not procude this, so I discarded that this is a sidebar.exe memory management, not, is an script bug.

When more time is running the gadget, more unresponsiveness becomes the gadget (on click).

My knowledges about JavaScript are NULL, but anyways I can understand the syntax and try to understand what the developer is doing in this code, I think that the problem is when managging the image objects, but at my point of view those objets seems to be properly disposed after each operation.

QUESTION

This is the gadget source.

Someone could help me to discover and fix what is causing the memory leak in this gadget?

sushi_driveinfo.html (main window):

<html>
  <head>
    <title>Drive Info</title>
    <style>
      body { margin: 0; padding: 0; width: 156px; height: 200px; background-image: url(images\canvas.png); color: #ffffff; font-family: 'Segoe UI'; }
      #targets { position: absolute; top: 0; left: 0; }
      .target { position: absolute; width: 156px; height: 48; left: 0; cursor: hand; }
    </style>
    <script type="text/javascript">
      var lst = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      var timeout = null;
      var drives = new Array(26);
      var drvchk = new Array(26);
      var drvspc = new Array(26);
      var vizchg = false;
      var current_y = 0;
      var background,theme,remove,local,network,media,show_pc,show_net;
      var item_height=48;
      var icon_offset=20;
      var text_offset=72;
      var meter_offset=24;

      function convertBytes(b)
      {
        var i = 0, u = Array(' MB', ' GB', ' TB');
        while (b >= 1024 && (b /= 1024) >= 1) i++;
        return (Math.round(b * 100) / 100) + u[i];
      }

      function openDrive()
      {        
        var d = window.event.srcElement.getAttribute('drive');    
        System.Shell.execute(d + ':\\');
        return;
      }

      function openNetwork()
      {        
        System.Shell.execute("Explorer", "/N,::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}");
        return;
      }

      function openComputer()
      {        
        System.Shell.execute("Explorer", "/N,::{20D04FE0-3AEA-1069-A2D8-08002B30309D}");
        return;
      }

      function recheckDrives() {
       for(var i = 0; i < 26; i++)
        {
          if (!drives[i]) {
            drives[i] = System.Shell.drive(lst.charAt(i));
            if (drives[i]) { vizchg = true; drvchk[i] = true; }
          } else {
          if (drives[i].isReady != drvchk[i]) { drvchk[i] = !drvchk[i]; vizchg = true; }
          if (drives[i].isReady && drives[i].freeSpace != drvspc[i]) { drvspc[i] = drives[i].freeSpace; vizchg = true; }
          }
        }
      }

      function calcHeight(h) {
        var y=0;
        if(show_pc==2) y+=h;
        if(show_net==2) y+=h;
        for(var i=0;i<26;i++)
            if(isDriveVisible(i)) y+=h;
        return y;
      }

      function isDriveVisible(i) {
        if(drvchk[i]) {
           if      (drives[i].driveType == 2 && remove == 1)  ;
           else if (drives[i].driveType == 3 && local == 1)   ;
           else if (drives[i].driveType == 4 && network == 1) ;
           else if (drives[i].driveType == 5 && media == 1)   ;
           else if (drives[i].driveType == 1 || drives[i].driveType == 6) ;
           else 
            return true;
        }
        return false;
      }

      function paintPC() {
        if (show_pc == 2) {
            canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);  
            var di=canvas.addImageObject('images/drives/pc'+ theme +'.png', icon_offset, current_y);
            di.width*=0.8;
            di.height*=0.8;
            canvas.addTextObject('Computer', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
            var b = document.createElement('DIV');
            b.className = 'target';
            b.style.posTop = current_y;
            b.onclick = openComputer;
            targets.appendChild(b);
            current_y+=item_height;
        }
        return;
      }

      function paintNET() {
        if (show_net == 2) {
            canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);  
            var di=canvas.addImageObject('images/drives/net'+ theme +'.png', icon_offset, current_y);
            di.width*=0.8;
            di.height*=0.8;
            canvas.addTextObject('Network', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
            var b = document.createElement('DIV');
            b.className = 'target';
            b.style.posTop = current_y;
            b.onclick = openNetwork;
            targets.appendChild(b);
            current_y+=item_height;
        }
        return;
      }

      function paintGadget()
      {       
      try {
        recheckDrives();
        if (!vizchg) return;

        var total_height=calcHeight(item_height);
        System.Gadget.beginTransition();

        document.body.style.height=total_height;
        canvas.style.height=total_height;
        canvas.removeObjects();
        targets.innerHtml = '';

        current_y = 0;
        paintPC();
        paintNET();
        for(i = 0; i < 26; i++)
        {
            if(isDriveVisible(i)) {
              if (drives[i].freeSpace != 0) {
               canvas.addImageObject('images/backgrounds/background' + background + '.png', 0, current_y);  
               var f = Math.round(drives[i].freeSpace / drives[i].totalSize * 100);
               var u = (100 - f); 
               canvas.addTextObject(convertBytes(drives[i].freeSpace) + ' / ' + f + '%', 'Segoe UI', 10, 'white', text_offset, current_y + 17);
               var m = canvas.addImageObject('images/meter' + (u < 90 ? 'blue': (u < 98 ? 'orange': 'red')) + '.png', meter_offset, current_y + 34);   
               m.width = Math.floor((u * 128 / 100));
               m.left = 24 - Math.floor(((128 - m.width) / 2));
              } else {
               canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);  
               canvas.addTextObject(convertBytes(drives[i].totalSize), 'Segoe UI', 10, 'white', text_offset, current_y + 17);
              }

              var di=canvas.addImageObject('images/drives/drive' + drives[i].driveType + theme + '.png', icon_offset, current_y-5);
              di.width*=0.8;
              di.height*=0.8;
              canvas.addTextObject(drives[i].volumeLabel + ' (' + drives[i].driveLetter + ':)', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
              var o = document.createElement('DIV');
              o.className = 'target';
              o.style.posTop = current_y;
              o.setAttribute('drive', drives[i].driveLetter);
              o.onclick = openDrive;
              targets.appendChild(o);

              current_y += item_height;
           }
        System.Gadget.endTransition(System.Gadget.TransitionType.morph,0.1);
        window.setTimeout(fixCanvasBackground, 600);
        }
        } finally {
         vizchg = false;
         return;
        }
      }

      function fixCanvasBackground() {
        canvas.src = canvas.src;
      } 

      function initDrives()
      {
       for(var i = 0; i < 26; i++)  {
            drives[i] = System.Shell.drive(lst.charAt(i));
            if (drives[i] && drives[i].isReady)
            { drvchk[i] = true ; drvspc[i] = drives[i].freeSpace; }
            else  { drvchk[i] = false; }
        }
        return;
      }

      function onShowSettings() {
        window.clearInterval(timeout);
        System.Gadget.beginTransition();
        window.setTimeout(endTransitionFast, 400);
      }

      function onSettingsClosed() {
        readSettings();
        timeout=window.setInterval(paintGadget, 2500);
        vizchg=true;
        paintGadget();
      }

      function endTransitionFast() {
        System.Gadget.endTransition(System.Gadget.TransitionType.morph, 0.1);
        fixCanvasBackground();
      }

    function readSettings() {
        background=System.Gadget.Settings.read("background");
        if(background==0) { background=2; System.Gadget.Settings.write("background",2); }
        theme=System.Gadget.Settings.read("theme");
        if(theme==0) { theme=1; System.Gadget.Settings.write("theme",1); }
        show_pc=System.Gadget.Settings.read("showpc");
        if(show_pc==0) { show_pc=1; System.Gadget.Settings.write("showpc",1); }
        show_net=System.Gadget.Settings.read("shownet");
        if(show_net==0) { show_net=1; System.Gadget.Settings.write("shownet",1); }
        local=System.Gadget.Settings.read("local");
        if(local==0) { local=2; System.Gadget.Settings.write("local",2); }
        media=System.Gadget.Settings.read("media");
        if(media==0) { media=2; System.Gadget.Settings.write("media",2); }
        network=System.Gadget.Settings.read("network");
        if(network==0) { network=2; System.Gadget.Settings.write("network",2); }
        remove=System.Gadget.Settings.read("remove");
        if(remove==0) { remove=2; System.Gadget.Settings.write("remove",2); }
      }

      function onLoad()
      {
        System.Gadget.settingsUI = "settings.html";
        System.Gadget.onSettingsClosed = onSettingsClosed;
        System.Gadget.onShowSettings = onShowSettings;

        readSettings();
        initDrives();
        timeout = window.setInterval(paintGadget, 2500);
        vizchg = true;
        paintGadget();
        return;
      }
    </script>
  </head>
  <body onload="onLoad()">
    <div id="targets"></div>
    <g:background id="canvas" src="images/canvas.png" style="position: absolute; top: 0; left: 0; width: 156; height: 200; z-index: -999;" opacity="0" />
  </body>
</html>

settings.html (settings window):

<html>
  <head>
    <style>
      body { width: 250px; height: 800px; padding: 0px; margin: 0px; font-family: Tahoma; }
      body,p,div,span,td { font-size: 9pt; }
      label { font-weight: bold; }
      input,select { font: Arial; font-size: 9pt; }
      table { width: 100%; }
    </style>
    <script>
      var background, maxBackgrounds = 3, theme = 1, maxThemes = 7;

      function updateBackground()
      {
        var x = 84, y = 47, m;
        canvas.removeObjects();

        canvas.addImageObject('images/backgrounds/background' + background + '.png', x, y); 
        m = canvas.addImageObject('images/meterblue.png', x + 24, y + 34);   
        m.width = (0.25 * 128);
        m.left = x + 24 - ((128 - m.width) / 2);

        canvas.addImageObject('images/drives/drive3' + theme + '.png', x, y);
        canvas.addTextObject('Vista (C:)', 'Segoe UI', 11, 'white', x + 58, y + 5);
        canvas.addTextObject('40GB / 75%', 'Segoe UI', 10, 'white', x + 58, y + 17);

        //y -= 20;

        //canvas.addImageObject('images/backgrounds/background' + background + '.png', x, y); 
        //m = canvas.addImageObject('images/meterorange.png', x + 24, y + 34);  
        //m.width = (0.937 * 128);
        //m.left = x + 24 - ((128 - m.width) / 2);

        //canvas.addImageObject('images/drives/drive3.png', x, y);
        //canvas.addTextObject('Apps (D:)', 'Segoe UI', 11, 'white', x + 58, y + 5);
        //canvas.addTextObject('10GB / 6.3%', 'Segoe UI', 10, 'white', x + 58, y + 17);

        canvas.addImageObject('images/drives/drive3' + theme + '.png', x-85, y+130);
        canvas.addImageObject('images/drives/drive2' + theme + '.png', x-85, y+172);
        canvas.addImageObject('images/drives/drive4' + theme + '.png', x-85, y+215);
        canvas.addImageObject('images/drives/drive5' + theme + '.png', x-85, y+258);
      }

      function onBackground()
      {
        var e = window.event, o = e.srcElement, b = o.getAttribute('base');

        o.src = 'images/settings/' + b + (e.type == 'mouseover' || e.type == 'mouseup' ? 'hover': (e.type == 'mousedown' ? 'pressed': '')) + '.png';

        if (e.type == 'mouseup') 
        {
          if (b == 'next') background++; else background--;
          if (background < 1) background = maxBackgrounds;
          if (background > maxBackgrounds) background = 1;

          updateBackground();        
        }
      }

      function onTheme()
      {
        var e = window.event, o = e.srcElement, b = o.getAttribute('base');

        o.src = 'images/settings/' + b + (e.type == 'mouseover' || e.type == 'mouseup' ? 'hover': (e.type == 'mousedown' ? 'pressed': '')) + '.png';

        if (e.type == 'mouseup') 
        {
          if (b == 'next') theme++; else theme--;
          if (theme < 1) theme = maxThemes;
          if (theme > maxThemes) theme = 1;

          updateBackground();        
        }
      }


      function onClose(event)
      {
        if (event.closeAction == event.Action.commit) 
        {
          System.Gadget.Settings.write("background", background);
          System.Gadget.Settings.write("theme",      theme);
          System.Gadget.Settings.write("showpc",     document.boxes.mypc.checked ? 2 : 1);
          System.Gadget.Settings.write("shownet",    document.boxes.netw.checked ? 2 : 1);

          System.Gadget.Settings.write("remove",     document.boxes.remove.checked ? 2 : 1);
          System.Gadget.Settings.write("local",      document.boxes.local.checked ? 2 : 1);
          System.Gadget.Settings.write("network",    document.boxes.network.checked ? 2 : 1);
          System.Gadget.Settings.write("media",      document.boxes.media.checked ? 2 : 1);
        }

        event.cancel = false;

//      System.Gadget.beginTransition();
//      window.setTimeout(endtransit, 400); 
      }

/*    function endtransit() {
        System.Gadget.endTransition(System.Gadget.TransitionType.morph, 0.1);
      }*/


      function onLoad() 
      {
        var box;
        System.Gadget.onSettingsClosing = onClose;

        background = System.Gadget.Settings.read("background");
        if (background == 0) background = 2;

        theme = System.Gadget.Settings.read("theme");
        if (theme == 0) theme = 1;

        System.Gadget.Settings.read("remove")  == 2 ? document.boxes.remove.checked  = true : false;
        System.Gadget.Settings.read("local")   == 2 ? document.boxes.local.checked   = true : false;
        System.Gadget.Settings.read("network") == 2 ? document.boxes.network.checked = true : false;
        System.Gadget.Settings.read("media")   == 2 ? document.boxes.media.checked   = true : false;

        System.Gadget.Settings.read("showpc")  == 2 ? document.boxes.mypc.checked   = true : false;
        System.Gadget.Settings.read("shownet") == 2 ? document.boxes.netw.checked   = true : false;

        updateBackground();
      }
    </script>
  </head>
  <body onload="onLoad()">
    <g:background id="canvas" src="images/settings/desktop.png" style="position: absolute; left: 1; top: 1; z-index: -999;" />
    <div style="position: absolute; left: 0; top: 147px;">
      <table cellspacing="0" cellpadding="0">
        <tr>
          <td style="width: 33%; padding-right: 10px;" align="right"><img src="images/settings/previous.png" base="previous" style="cursor: hand;" onmouseover="onBackground();" onmouseout="onBackground();" onmousedown="onBackground();" onmouseup="onBackground();" /></td>
          <td style="width: 33%;" align="center"><label>Backgrounds</label></td>
          <td style="width: 33%; padding-left: 10px;" align="left"><img src="images/settings/next.png" base="next" style="cursor: hand;" onmouseover="onBackground();" onmouseout="onBackground();" onmousedown="onBackground();" onmouseup="onBackground();" /></td>
         </tr>
         <tr>
          <td style="width: 33%; padding-right: 10px;" align="right"><img src="images/settings/previous.png" base="previous" style="cursor: hand;" onmouseover="onTheme();" onmouseout="onTheme();" onmousedown="onTheme();" onmouseup="onTheme();" /></td>
          <td style="width: 33%;" align="center"><label>Icon Theme</label></td>
          <td style="width: 33%; padding-left: 10px;" align="left"><img src="images/settings/next.png" base="next" style="cursor: hand;" onmouseover="onTheme();" onmouseout="onTheme();" onmousedown="onTheme();" onmouseup="onTheme();" /></td>
        </tr>
      </table>
      <table cellspacing="0" cellpadding="0" style="margin-top: 15px;margin-left:60px;">
        <tr><td>
          <form name="boxes">
            <input type="checkbox" name="local">
                <font style="font-size: 8pt;">Local Drives</font><p>
            <input type="checkbox" name="remove">
                <font style="font-size: 8pt;">Removable Drives</font><p>
            <input type="checkbox" name="network">
                <font style="font-size: 8pt;">Network Drives</font><p>
            <input type="checkbox" name="media">
                <font style="font-size: 8pt;">Media Drives</font><p>
            <input type="checkbox" name="mypc">
                <font style="font-size: 8pt;">My Computer link</font><br>
            <input type="checkbox" name="netw">
                <font style="font-size: 8pt;">Network Link</font>
            </form>
        </td></tr>
      </table>
    </div>
  </body>
</html>

UPDATE:

Here is the full gadget source if it helps:

https://www.mediafire.com/?c8h1271714sp6tz

ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • try to use xperf/WPA to analyze memory usage: http://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-49-WPT-Memory-Analysis-VirtualAlloc, http://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-50-WPT-Memory-Analysis-Heap – magicandre1981 Dec 21 '14 at 20:20
  • @magicandre1981 I've downloaded and installed WinSDK to install xperf, but this tool seems for experts on memory profiling/debugging, even seeing the examples you've provided I don't understand how to do it work for test the gadget, thanks anyways! – ElektroStudios Dec 22 '14 at 01:37

1 Answers1

3

There is always a chance that their is a leak in one of the drivers installed on your system that causes this. However, when looking at that javascript code there is a pattern that caused issues in the past and has a resolution now.

The main loop of the Gadget looks like this:

function paintGadget() {
    // repaint/rebuild all UI elelments
    // remove all elements
    targets.innerHtml = '';
    // buildup
     var o = document.createElement('DIV');
     o.onclick = openDrive;
     targets.appendChild(o);
}

function openDrive() {
}

window.setInterval(paintGadget, 2500);

which basically means: call paintGadget every 2.5 seconds, for ever

This should be fine if the javascript engine and its resources are garbage collected when they are no longer in any scope. And this where things might go wromg due to sloppy programming.

Based on the answer from user dsg we learn that eventlisteners are a root cause for garbage collection to fail.

To overcome this problem we have to replace the line targets.innerHtml = ''; in the function paintGadget with an implementation that removes the eventhandlers on every element before removing the element it self, like so:

while(targets.firstChild) {
    var ch = targets.firstChild;
    ch.onclick = null;
    targets.removeChild(ch);              
}

As said in the introduction, paintGadget does more in particular with the canvas where it follows a similar pattern, remove everything and recreate. If there is a leak in there a reimplementation of that is needed as well.

Community
  • 1
  • 1
rene
  • 41,474
  • 78
  • 114
  • 152
  • Thankyou for your answer. (Sorry for my english) The gadget just always have growed up its RAM consumption up to no limit doing nothing just keeping running the gadget and it will reach 1 GB RAM at determinated pc up-time. My knowledges about JS are null I'm just looking for a solution to solve this, but If I understood good I only need to replace literally that instruction in the paintGadget function with the loop that you've provided, then I don't need to reset the innerHTML property anymore? – ElektroStudios Dec 23 '14 at 15:28
  • Unfortunately I need to spent a day or more to test the results on my side, I've implemented your code modification and the first thing that I see seems really good, the sidebar.exe RAM normally starts with 11 MB, this gadget is increasing the RAM up to 14 mb and then it decreases to 11 mb everytime, that is caused by your modification so maybe now is solved, but I need to spent time to discover if when it decreases the ram maybe it stores garbage, I mean that maybe now it can decrease RAM to 11 mb but maybe in 24h. it can only decrease the ram to 50 mb,and that will denote a RAM problem still – ElektroStudios Dec 23 '14 at 15:36
  • As I said is to soon to say its solved or not for me, but do you think that maybe other parts of the code needs a modification or the modification that you've provided could be enough? treally thanks for your interest in this question. PS: I don't know how a dispositive can cause a leak in a gadget, but I'm sure it is not the case because I'm having this issue with this gadget for years using Win7 and Win8 with only 1 dispositive connected/monitored, and not only in my pc, it was clearly a gadget issue about RAM management, I wonder that your modification could solve this problem forever. – ElektroStudios Dec 23 '14 at 15:38
  • Its just amazing, RAM consumption never increased more than 15 mb, and always returns down to 11-12 mb, thankyou forever! – ElektroStudios Dec 24 '14 at 07:56