I have a basic data logger system that I originally had running on a Fez Rhino where it would log sucessfully for hours. It basically reads incoming data on a serial port that is being sent at 100Hz with 80 byte packets and then stores the incoming packets to an SD card and transmits the packet out another serial port at 20Hz, i.e. every 5th packet is stored and transmitted with some analog sampling at the same time.
I’m using the same code now on the Hydra, with the only difference being that I’m using the relevant Gadgeteer classes for opening the serial ports etc.
What I’ve noticed is that even when I pare the data logging code right down to simply opening a single serial port and read the 100Hz input without logging it to the SD card and without transmitting it out another serial port I see a memory leak and get an out of memory exception after running for a while.
So in ProgramStarted I create and open the serial port and create a thread to perform the data logging:
_rockwellComPort = new Serial(GetSocket(6), 115200, Serial.SerialParity.None, Serial.SerialStopBits.One, 8, Serial.HardwareFlowControl.NotRequired, null);
_rockwellComPort.Open();
_loggerThread = new Thread(new ThreadStart(StartLogging));
_loggerThread.Start();
The code for the data logging thread simply sits in a loop reading from the serial port, for now it doesn’t write it to the SD card and it doesn’t transmit it out another serial port:
void StartLogging()
{
byte[] buf = new byte[BUFFER_SIZE];
Debug.Print(Debug.GC(true).ToString());
while (true)
{
_rockwellComPort.Read(buf, 0, 1);
if (buf[0] == SYNC_BYTE)
{
_rockwellComPort.Read(buf, 1, 6);
if (buf[5] == INS_PACKET_TYPE)
{
int cDataBytes = buf[6];
// Now read rest of packet including 16bit CRC
int bytesLeft = cDataBytes + 2;
int totalPacketSize = HEADER_SIZE + bytesLeft;
if (totalPacketSize < BUFFER_SIZE)
{
while (bytesLeft > 0)
{
int bytesRead = _rockwellComPort.Read(buf, totalPacketSize - bytesLeft, bytesLeft);
bytesLeft -= bytesRead;
}
}
else
{
Debug.Print("Bogus size");
}
}
// Reset packet
for (int i = 0; i < buf.Length; i++)
buf[i] = 0;
}
}
}
As you can see there is a single buffer created at the start of the thread routine which is used for each incoming packet, i.e. I’m not creating a new buffer for each incoming packet.
The GC seems to kick in roughly once a minute and looking at the GC report it looks like the BINARY_BLOB_HEAD keeps increasing, it starts off at startup at 378,804 bytes and increases by just under 300KB each minute.
GC: 2msec 446076 bytes used, 5845056 bytes available
Type 0F (STRING ): 1680 bytes
Type 11 (CLASS ): 17532 bytes
Type 12 (VALUETYPE ): 1512 bytes
Type 13 (SZARRAY ): 5880 bytes
Type 01 (BOOLEAN ): 36 bytes
Type 03 (U1 ): 432 bytes
Type 04 (CHAR ): 780 bytes
Type 06 (U2 ): 576 bytes
Type 07 (I4 ): 1044 bytes
Type 08 (U4 ): 108 bytes
Type 0C (R8 ): 360 bytes
Type 0F (STRING ): 60 bytes
Type 11 (CLASS ): 2400 bytes
Type 12 (VALUETYPE ): 84 bytes
Type 15 (FREEBLOCK ): 5845056 bytes
Type 16 (CACHEDBLOCK ): 72 bytes
Type 17 (ASSEMBLY ): 31260 bytes
Type 18 (WEAKCLASS ): 96 bytes
Type 19 (REFLECTION ): 168 bytes
Type 1B (DELEGATE_HEAD ): 720 bytes
Type 1D (OBJECT_TO_EVENT ): 576 bytes
Type 1E (BINARY_BLOB_HEAD ): 378804 bytes
Type 1F (THREAD ): 1536 bytes
Type 20 (SUBTHREAD ): 192 bytes
Type 21 (STACK_FRAME ): 1104 bytes
Type 22 (TIMER_HEAD ): 72 bytes
Type 27 (FINALIZER_HEAD ): 504 bytes
Type 31 (IO_PORT ): 612 bytes
Type 34 (APPDOMAIN_HEAD ): 72 bytes
Type 36 (APPDOMAIN_ASSEMBLY ): 3684 bytes
GC: 40msec 696732 bytes used, 5594400 bytes available
Type 0F (STRING ): 1512 bytes
Type 11 (CLASS ): 17172 bytes
Type 12 (VALUETYPE ): 1512 bytes
Type 13 (SZARRAY ): 5880 bytes
Type 01 (BOOLEAN ): 36 bytes
Type 03 (U1 ): 432 bytes
Type 04 (CHAR ): 780 bytes
Type 06 (U2 ): 576 bytes
Type 07 (I4 ): 1044 bytes
Type 08 (U4 ): 108 bytes
Type 0C (R8 ): 360 bytes
Type 0F (STRING ): 60 bytes
Type 11 (CLASS ): 2400 bytes
Type 12 (VALUETYPE ): 84 bytes
Type 15 (FREEBLOCK ): 5594400 bytes
Type 16 (CACHEDBLOCK ): 48 bytes
Type 17 (ASSEMBLY ): 31260 bytes
Type 18 (WEAKCLASS ): 96 bytes
Type 19 (REFLECTION ): 168 bytes
Type 1B (DELEGATE_HEAD ): 684 bytes
Type 1D (OBJECT_TO_EVENT ): 576 bytes
Type 1E (BINARY_BLOB_HEAD ): 628560 bytes
Type 1F (THREAD ): 1920 bytes
Type 20 (SUBTHREAD ): 192 bytes
Type 21 (STACK_FRAME ): 2124 bytes
Type 22 (TIMER_HEAD ): 72 bytes
Type 23 (LOCK_HEAD ): 60 bytes
Type 24 (LOCK_OWNER_HEAD ): 24 bytes
Type 26 (WAIT_FOR_OBJECT_HEAD): 48 bytes
Type 27 (FINALIZER_HEAD ): 456 bytes
Type 31 (IO_PORT ): 612 bytes
Type 34 (APPDOMAIN_HEAD ): 72 bytes
Type 36 (APPDOMAIN_ASSEMBLY ): 3684 bytes
As I mentioned this same code runs fine for hours on the Fez Rhino which has a whole lot less memory and never sees an out of memory exception.