Memory leak - Netduino Plus 2 (and Netduino Plus 1) - Netduino Forums
   
Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Memory leak

memory leak out of memory

  • Please log in to reply
29 replies to this topic

#1 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 10 January 2013 - 10:08 AM

Hi there,

 

I am building a live data logger (basically a counter) which sends counts (collected by a photo sensor) to a webpage in a querystring so the data can be added to a SQL DB.

 

To make sure that I have a redundancy, in the event of power failure for example, I have two files on an SD card which the counter writes to on the photo sensor interrupt. Periodically the SD card is read and the data put into an array which is then looped through to send the HTTP requests. This prevents any issues of trying to read from the SD card at the same time as a photo interrupt is trying to write to the card.

 

I'm using a Netduino Plus 2.

 

At the moment I am testing a part of the final code and have encountered a "memory leak" issue.

 

Basically I am simply looping through the "SD Card read" and "array send threads" to test them. the problem I am having is that I eventually run out of memory and I can't understand why.

 

I would have thought that the only thing taking up memory would be the counter variable (which is the only thing that changes with each request... it's increased by one) but for whatever reason on each loop I lose about 3500 bytes.

 

I have made sure that I flush/close/dispose both the stream reader and stream writer (used for writing errors to a log file) when I am finished using them. I also clear the array when i am done with it.

 

Surely increasing the counter variable by an increment of 1 is not 3500 bytes worth of memory?

 

Do I need to abort/close threads as I leave them? Is it something to do with the "Socket" or "WebSession"... do I need to somehow terminate/clear these before creating new ones?

 

using System;using System.Collections;using System.IO;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using Microsoft.SPOT;using Microsoft.SPOT.Hardware;using SecretLabs.NETMF.Hardware;using SecretLabs.NETMF.Hardware.Netduino;using Toolbox.NETMF.Hardware;using Toolbox.NETMF.NET;using VariableLabs.PowerManagment;namespace testToolbox21240{    public class Program    {        static WiFlyGSX WifiModule;        static ArrayList counterDataArray = new ArrayList();        // SD Card file variables        static string strWriteFile = "datatwo";        static string strReadFile = "dataone";        // WiFi SSID and password        static string strSSID = "mySSID";        static string strPassword = "myPassword";        // Remote host URL        static string strRemoteHost = "myUrl.com";        // Counter variables        static uint intCounter = 0;        static uint intTimeoutCounter = 0;                        public static void Main()        {            Debug.Print("Turn off the Ethernet controller");            PowerManagment.SetPeripheralState(Peripheral.Ethernet, false);            Debug.EnableGCMessages(true);            connectToWifi();            populateArray();                                  Thread.Sleep(Timeout.Infinite);        }                public static void populateArray()        {            Debug.Print("Populate array from: " + strReadFile);            using (var filestream = new FileStream(@"SD" + strReadFile + ".txt", FileMode.Open))            {                string strSendLine;                StreamReader sr = new StreamReader(filestream);                while ((strSendLine = sr.ReadLine()) != null)                {                    counterDataArray.Add(strSendLine);                }                sr.Close();                sr.Dispose();            }            Debug.Print("send data from array");                        foreach (string value in counterDataArray)            {                httpSendData(value);            }                        cleanUp();                    }        public static void httpSendData(string queryStringData)        {            SimpleSocket Socket = new WiFlySocket(strRemoteHost, 80, WifiModule);            HTTP_Client WebSession = new HTTP_Client(Socket);            try            {                HTTP_Client.HTTP_Response Response = WebSession.Get("/default.aspx?" + queryStringData);                Debug.Print("value: " + queryStringData);            }            catch (System.ApplicationException e)            {                Debug.Print("Error Code: " + e);                intTimeoutCounter++;                Debug.Print("intTimeoutCounter: " + intTimeoutCounter);                using (var filestream = new FileStream(@"SDlog.txt", FileMode.Append))                {                    StreamWriter sw = new StreamWriter(filestream);                    sw.WriteLine("DateTime: " + DateTime.Now.AddHours(2) + "rnError Code: " + e + "rnintTimeoutCounter: " + intTimeoutCounter + "rn");                    sw.Flush();                    sw.Close();                    sw.Dispose();                }                Thread.Sleep(1500);                httpSendData(queryStringData);            }            catch (System.NullReferenceException f)            {                Debug.Print("Error Code: " + f);                using (var filestream = new FileStream(@"SDlog.txt", FileMode.Append))                {                    StreamWriter sw = new StreamWriter(filestream);                    sw.WriteLine("DateTime: " + DateTime.Now.AddHours(2) + "rnError Code: " + f + "rnintTimeoutCounter: " + intTimeoutCounter + "rn");                    sw.Flush();                    sw.Close();                    sw.Dispose();                }                        }            finally            {                intCounter++;            }                     }        public static void cleanUp()        {            if (strWriteFile == "dataone")            {                strWriteFile = "datatwo";                strReadFile = "dataone";            }            else            {                strWriteFile = "dataone";                strReadFile = "datatwo";            }            counterDataArray.Clear();            Debug.GC(true);            Debug.Print("Free Memory: " + Debug.GC(true).ToString());            Debug.Print("Record count: " + intCounter);            Debug.Print("running thread count: " + ThreadState.Background.ToString());            populateArray();        }        public static void connectToWifi()        {            Debug.Print("start wifi");            WifiModule = new WiFlyGSX();            WifiModule.EnableDHCP();            WifiModule.JoinNetwork(strSSID, 0, WiFlyGSX.AuthMode.MixedWPA1_WPA2, strPassword);            Thread.Sleep(3500);            // Showing some interesting output            Debug.Print("Local IP: " + WifiModule.LocalIP);            Debug.Print("MAC address: " + WifiModule.MacAddress);            SNTP_Client TimeClient = new SNTP_Client(new WiFlySocket("ntp2.is.co.za", 123, WifiModule));            Thread.Sleep(500);            TimeClient.Synchronize();            Thread.Sleep(500);            Debug.Print("DateTime.Now: " + DateTime.Now.AddHours(2));            Thread.Sleep(3500);        }    }}

 

A snippet of the debug output:

 

 

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Turn off the Ethernet controller
start wifi
Local IP: 192.168.0.8
MAC address: 00:60:06:80:88:55
DateTime.Now: 01/10/2013 12:02:16
Populate array from: dataone
send data from array
value: deviceID=1&count=0&dateTimeStamp=12/5/2012%2017:8:5
value: deviceID=1&count=1&dateTimeStamp=12/5/2012%2017:8:6
value: deviceID=1&count=2&dateTimeStamp=12/5/2012%2017:8:7
value: deviceID=1&count=3&dateTimeStamp=12/5/2012%2017:8:7
value: deviceID=1&count=4&dateTimeStamp=12/5/2012%2017:8:8
[color=#ff0000;]Free Memory: 99072[/color]
Record count: 5
running thread count: 4
Populate array from: datatwo
send data from array
value: deviceID=1&count=5&dateTimeStamp=12/7/2012%2013:31:33
value: deviceID=1&count=6&dateTimeStamp=12/7/2012%2013:31:34
value: deviceID=1&count=7&dateTimeStamp=12/7/2012%2013:32:24
value: deviceID=1&count=8&dateTimeStamp=12/7/2012%2013:32:24
value: deviceID=1&count=9&dateTimeStamp=12/7/2012%2013:32:24
value: deviceID=1&count=10&dateTimeStamp=12/7/2012%2013:32:24
value: deviceID=1&count=11&dateTimeStamp=12/7/2012%2013:32:24
value: deviceID=1&count=12&dateTimeStamp=12/7/2012%2013:32:25
value: deviceID=1&count=13&dateTimeStamp=12/7/2012%2013:32:25
value: deviceID=1&count=14&dateTimeStamp=12/7/2012%2013:32:25
value: deviceID=1&count=15&dateTimeStamp=12/7/2012%2013:32:25
value: deviceID=1&count=16&dateTimeStamp=12/7/2012%2013:32:25
[color=#ff0000;]Free Memory: 95544[/color]
Record count: 17
running thread count: 4

 

 

Thanks for any advice!

 

Donovan



#2 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7767 posts
  • LocationNew York, NY

Posted 10 January 2013 - 11:23 AM

Hi Donovan, To help debug...I'd recommend printing out your free RAM more often during your project, to see where it increases/decreases. With NETMF 4.2 and 100% managed code, you should get full garbage collection of all dereferenced objects. Chris

#3 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 10 January 2013 - 11:55 AM

d'oh!!  :blink:

 

What a good idea, I'll give that a try and let you know where it swallows up the RAM.

 

Thanks Chris!!



#4 Cuno

Cuno

    Advanced Member

  • Members
  • PipPipPip
  • 144 posts
  • LocationZürich / Switzerland

Posted 10 January 2013 - 12:51 PM

I'd recommend printing out your free RAM more often

 

Yes, but... In violation of the C# language report, NETMF causes an allocation whenever you reference a string literal. This could be a reason why you see memory allocated in programs that shouldn't. These temporary objects will be reclaimed upon the next run of the garbage collector.

 

This is a mostly harmless, but unfortunately not documented difference between NETMF and the full .NET framework (http://netmf.codeple...m/workitem/1018).

 

Cuno



#5 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 10 January 2013 - 01:21 PM

Hi Cuno,

 

I noticed this after printing out free RAM in various places and then removing a lot of these "Debug.Print" statements... I even stopped printing out the querystring and now am only losing about 852 bytes per cycle... still seems strange to be losing so much.

 

I've also thought about adding some delay's into the project hoping that this will result in threads "dying" naturally so the next time around it creates them fresh?



#6 NooM

NooM

    Advanced Member

  • Members
  • PipPipPip
  • 490 posts
  • LocationAustria

Posted 10 January 2013 - 01:24 PM

you can force the gc.. Debug.GC(true);



#7 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 10 January 2013 - 01:42 PM

Hi NooM,

 

I have tried this in various places but still seem to end up losing 3528 bytes in the first loop... and then consistently 852 bytes for subsequent loops.

 

I even tried removing the counter variable (which is increased by 1 each loop after a successful HTTP request) but this does not affect the memory leak... so it's something else that is eating up the memory??



#8 NooM

NooM

    Advanced Member

  • Members
  • PipPipPip
  • 490 posts
  • LocationAustria

Posted 10 January 2013 - 03:13 PM

yes, and something that the gc doesent removes.

 

i had a simmiliar bug with an serial interrupt.

maybe you can redisgn your code?



#9 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 10 January 2013 - 03:23 PM

I'm looking at how else I can re-do the code but I'm not sure what else to try...

 

...the querystring is generated (from the array) and then sent to the server via HTTP. That's already very simple.

 

Is there anyway to abort all threads and then start the "Main" thread again? I'm thinking of a sort of "soft" reset of the Netduino... this is a pain though as it means resetting the wifi connection as well as the counter.

 

I just can't understand what is hanging onto data and adding to it each loop. When a Thread gets to the end surely it releases any resources it was using?

 

I did notice when I did a Thread.count in the "Clean up" thread that is should 4 threads... how do I get it to drop a thread?



#10 skobyjay

skobyjay

    Advanced Member

  • Members
  • PipPipPip
  • 53 posts

Posted 18 January 2013 - 02:15 PM

Just curious, Have you tried to use the standard .Net HTTP request and response objects instead of the simple socket classes? I would be curious how the application behaves then. An idea I have is instead of creating completely new HTTP objects and socket connections for each line of data, which sounds expensive. Have you tried to only create one HTTP request and use that over the life of your application And have your foreach just use the same HTTP object and socket object and only changing the query string values? Have you used a tool like red gate memory profiler or performance profiler? These tools have been helpful for me in the past.

#11 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 18 January 2013 - 03:54 PM

as far as i know you have to use simpleSocket in conjunction with a wifi connection?



#12 skobyjay

skobyjay

    Advanced Member

  • Members
  • PipPipPip
  • 53 posts

Posted 19 January 2013 - 02:38 AM

okay, thats fine. just refactor your code so the code below is okay execute once.

 

SimpleSocket Socket = new WiFlySocket(strRemoteHost, 80, WifiModule);            HTTP_Client WebSession = new HTTP_Client(Socket);            

 

 

Then put this part in your "foreach"

 

try            {                HTTP_Client.HTTP_Response Response = WebSession.Get("/default.aspx?" + queryStringData);                Debug.Print("value: " + queryStringData);            }

 

I am curious about what result you will get. Oh and just for laughs remove all the try catches for now, just keep it simple. I can't imagine you are sending that much data, if you don't mind, what does your typical fine line look like and what size is the file?



#13 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 21 January 2013 - 01:40 PM

Hi skobyjay,

 

Thanks for the suggestion...

 

I have moved the socket and websession out of the "send" routine and only create them once now when I first connect to the wifi network. This has helped reduce the memory leak per loop to just 684 bytes.

 

The problem is that something is still taking up memory which will mean the device cannot run all day as we'd like it to without a restart.

 

Further to that the HTTP request is very slow and the Netduino takes between 1-2 seconds to process the request. This means that we'll not be able to cycle all the requests fast enough when the count tally could be increasing by 1 a second.

 

The average querystring looks like this: deviceID=1&count=6&dateTimeStamp=12/7/2012%2013:31:34

 

I have to be able to get the FTP working over the wifi so that I can upload a text file with the querystrings in it and then have the server handle the processing. It's too much for the Netduino to do. It would be fine if the sensor was only collecting data every minute or so, but with it collecting a count every second it's going to overwhelm the Netduino.



#14 skobyjay

skobyjay

    Advanced Member

  • Members
  • PipPipPip
  • 53 posts

Posted 21 January 2013 - 03:32 PM

Hmm, let me think about this. I'll try to make a sample app to reproduce, see if I can optimize anything for you.

#15 skobyjay

skobyjay

    Advanced Member

  • Members
  • PipPipPip
  • 53 posts

Posted 21 January 2013 - 03:44 PM

Can you post an update of your source? I'm wondering if something else could be rearranged. Also have you written a console application that performs the same action as your Netduino application code? I'm wondering if your server that handles the http request might be the bottle neck. It probably isn't but it's worth ruling that out while you are testing for performance. I would time these requests and response times.

#16 skobyjay

skobyjay

    Advanced Member

  • Members
  • PipPipPip
  • 53 posts

Posted 21 January 2013 - 07:39 PM

Maybe you could try to just have the Netduino copy the files to a network share and then have a scheduled console application that reads the files and executes the http requests.

#17 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 22 January 2013 - 06:49 AM

I don't think the HTTP delay has anything to do with the server, but rather the time the wifly module takes to process the request. I have changed to the remote host from the Toolbox example and it's still taking about a second per HTTP request.

 

Also since stripping out the try/catch statements the memory leak has gone from 684 bytes to a whopping 1728 bytes??!!

 

Here's the code:

 

using System;using System.Collections;using System.IO;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using Microsoft.SPOT;using Microsoft.SPOT.Hardware;using SecretLabs.NETMF.Hardware;using SecretLabs.NETMF.Hardware.Netduino;using Toolbox.NETMF.Hardware;using Toolbox.NETMF.NET;using VariableLabs.PowerManagment;namespace testToolbox21240{    public class Program    {        // Hardware: wifly, sensor and LCD        static WiFlyGSX WifiModule;        // SD Card file variables        static string strWriteFile = "datatwo";        static string strReadFile = "dataone";        // WiFi SSID and password        static string strSSID = "mySSID";        static string strPassword = "myPasssowrd";        // Remote host URL        static string strRemoteHost = "www.netmftoolbox.com";        // Counter variables        static uint intCounter = 0;        static ArrayList counterDataArray = new ArrayList();        public static SimpleSocket Socket;        public static HTTP_Client WebSession;                        public static void Main()        {            PowerManagment.SetPeripheralState(Peripheral.Ethernet, false);            Debug.EnableGCMessages(true);            connectToWifi();                                              Thread.Sleep(Timeout.Infinite);        }                public static void populateArray()        {            Debug.Print("Populate array from: " + strReadFile);            using (var filestream = new FileStream(@"SD" + strReadFile + ".txt", FileMode.Open))            {                string strSendLine;                StreamReader sr = new StreamReader(filestream);                while ((strSendLine = sr.ReadLine()) != null)                {                    counterDataArray.Add(strSendLine);                }                sr.Close();                sr.Dispose();            }            Debug.Print("send data from array");            httpSendData();        }        public static void httpSendData()        {                foreach (string value in counterDataArray)                {                    HTTP_Client.HTTP_Response Response = WebSession.Get("/helloworld/");                    Debug.Print("value: " + value);                    Debug.GC(true);                }                cleanUp();                                }        public static void cleanUp()        {            if (strWriteFile == "dataone")            {                strWriteFile = "datatwo";                strReadFile = "dataone";            }            else            {                strWriteFile = "dataone";                strReadFile = "datatwo";            }            counterDataArray.Clear();            Debug.Print("Free Memory: " + Debug.GC(true).ToString());            Debug.Print("Record count: " + intCounter);            Debug.GC(true);            populateArray();        }        public static void connectToWifi()        {            WifiModule = new WiFlyGSX();            WifiModule.EnableDHCP();            WifiModule.JoinNetwork(strSSID, 0, WiFlyGSX.AuthMode.MixedWPA1_WPA2, strPassword);            Thread.Sleep(3500);            // Showing some interesting output            Debug.Print("Local IP: " + WifiModule.LocalIP);            Debug.Print("MAC address: " + WifiModule.MacAddress);            SNTP_Client TimeClient = new SNTP_Client(new WiFlySocket("ntp2.is.co.za", 123, WifiModule));            Thread.Sleep(1000);            TimeClient.Synchronize();            Debug.Print("DateTime.Now: " + DateTime.Now.AddHours(2));            Socket = new WiFlySocket(strRemoteHost, 80, WifiModule);            WebSession = new HTTP_Client(Socket);            populateArray();        }    }}


#18 don664

don664

    Advanced Member

  • Members
  • PipPipPip
  • 77 posts
  • LocationHillcrest, KZN, South Africa

Posted 22 January 2013 - 06:57 AM

Thanks for the suggestion but sending the files to a server share (not even sure where to begin with this) to process is not an option because it will mean having to install a machine on site to handle this and that defeats the object of wanting the devices to be "free standing" with their only requirements being power and a wifi network.

 

If we're having to install a machine on site to handle the processing we may as well install cabling and simply run the whole project over ethernet. Again though this increases the overheads and installation times and makes a simple mechanical counter and piece of paper the best solution.

 

It's looking more and more likely that the wifi capabilities of the Netduino are not yet up to the task we're asking of it  :(

 

I'm going to look at the Arduino today as I think there is a way for the Arduino to run the wifly module in CMD mode which in turn can handle FTP requests. I hate the Arduino interface though but may have no choice now.

 

Thanks again for looking into this... hopefully I'm missing something?



#19 skobyjay

skobyjay

    Advanced Member

  • Members
  • PipPipPip
  • 53 posts

Posted 22 January 2013 - 03:25 PM

No problem, let me look at this code tonight, and give it more thought, Throughout the day I like a good challenge. Wow that's crazy removing the try catch increased the request size! Never heard anything like that. I know that throwing exceptions can be expensive but not having a try catch in the code that doesn't throw errors.. Confusing I'll get back to you.

#20 skobyjay

skobyjay

    Advanced Member

  • Members
  • PipPipPip
  • 53 posts

Posted 22 January 2013 - 04:29 PM

http://www.dotnetper...om/array-memory

We might be splitting hairs but check this article out. You might see a bump in performance by using a standard string array so that memory doesn't have to be allocated and indexes have their positions changed when items are added.





Also tagged with one or more of these keywords: memory, leak, out of memory

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.