Is there a memory leak when using thread local storage?

  • Thread starter Thread starter Florian Fordermaier
  • Start date Start date
F

Florian Fordermaier

Hello group,

a few days ago I realized that I (possibly) have a memory leak in my cf
application. I first thought it is somewhere in my interop code(doing
data exchange between native and managed application using shared
memory) but after lots of testing of only this unit of code I could not
find any leak. I went on looking for other possible causes and have
found something of what I'm not quite sure if it is a leak or if I'm
just misunderstanding things or using them in a false way. I wrote a
little test application which code I will provide at the end of the
post.(sorry for the long post)
The application uses a simple timer which puts the value of
GC.GetTotalMemory(false) in a textbox so I can see the value. It is
increasing as the timer is running every 300ms. I also put a button in
my application that triggers a GC.Collect() on click. If only running
the timer and clicking the button from now to then, the value after
collection is always the same.
The last button on my form allocates a LocalDataStoreSlot and starts 4
threads. Each of the threads just puts data in it, waits for 100ms,
then reads the data again. Each thread does this 100 times.
I tested both ways of allocating a data slot: Thread.AllocateDataSlot()
and Thread.AllocateNamedDataSlot("MySlot")
Only the latter way has an explicit free method
Thread.FreeNamedDataSlot, which i call if my test class is disposed(I
implemented this with a busy wait in a using statement, so the class is
disposed after the threads have finished). When I click the button
which forces a GC.Collect the value is about 1200 higher than before.
It increases again due to the timer but if I force the collection
again, the value remains constant. So I again triggered the execution
of the class containing the threads and after they are finished and I
force a collection, the value is again about 1200 higher than before.
Again, this value remains the constant lower bound until I again start
those threads making use of local data slots.
Is this "explainable" behaviour because I'm doing something wrong here
or misunderstand something or is something leaking.
My "real" application crashes depending on load after about 2-3 hours
with an OutOfMemoryException.
Does anybody have an idea of whats going on here?
Many thanks in advance
Florian Fordermaier


// source of test application

namespace TLSLeakTest // designer code of
form
{
partial class Form1
{
/// <summary>
/// Erforderliche Designervariable.
/// </summary>
private System.ComponentModel.IContainer components = null;

/// <summary>
/// Verwendete Ressourcen bereinigen.
/// </summary>
/// <param name="disposing">True, wenn verwaltete Ressourcen
gelöscht werden sollen; andernfalls False.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Vom Windows Form-Designer generierter Code

/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor
geändert werden.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.timer1 = new System.Windows.Forms.Timer();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(154, 16);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(72, 20);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.Click += new
System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(27, 16);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(100, 23);
this.textBox1.TabIndex = 1;
this.textBox1.Text = "textBox1";
//
// timer1
//
this.timer1.Enabled = true;
this.timer1.Interval = 300;
this.timer1.Tick += new
System.EventHandler(this.timer1_Tick);
//
// button2
//
this.button2.Location = new System.Drawing.Point(27, 58);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(199, 20);
this.button2.TabIndex = 2;
this.button2.Text = "button2";
this.button2.Click += new
System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F,
96F);
this.AutoScaleMode =
System.Windows.Forms.AutoScaleMode.Dpi;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(333, 186);
this.Controls.Add(this.button2);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);

}

#endregion

private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Timer timer1;
private System.Windows.Forms.Button button2;
}
}

//
*************************************************************************************
// code of form

using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace TLSLeakTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void timer1_Tick(object sender, EventArgs e)
{
textBox1.Text = GC.GetTotalMemory(false).ToString();
}

private void button1_Click(object sender, EventArgs e)
{
GC.Collect();
}

private void button2_Click(object sender, EventArgs e)
{
using(TestClass tc = new TestClass() )
{
while (!tc.IsFinished)
{
Thread.Sleep(1000);
}
Console.WriteLine("tc has finished.");
}

}
}
}

//
*************************************************************************************
// code of test class

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace TLSLeakTest
{
class DummyClass
{
internal string str = "hallo du";
internal int i = 876;

public DummyClass()
{
}
}

class TestClass : IDisposable
{
//private static LocalDataStoreSlot _tls;
private int _finCount = 0;
public TestClass()
{
//_tls = Thread.AllocateNamedDataSlot("MySlot");
Thread.AllocateNamedDataSlot("MySlot");

Thread t1 = new Thread(new ThreadStart(ThreadProc1));
Thread t2 = new Thread(new ThreadStart(ThreadProc2));
Thread t3 = new Thread(new ThreadStart(ThreadProc3));
Thread t4 = new Thread(new ThreadStart(ThreadProc4));

t1.Start();
t2.Start();
t3.Start();
t4.Start();


}

public static DummyClass SlotData
{
get
{
LocalDataStoreSlot _tls =
Thread.GetNamedDataSlot("MySlot");
object o = Thread.GetData(_tls);
if (o == null)
return null;
else
return (DummyClass)o;
}
set
{
//Thread.SetData(_tls, null); // helps nothing !
LocalDataStoreSlot _tls =
Thread.GetNamedDataSlot("MySlot");
Thread.SetData(_tls, value);
}
}

void ThreadProc1()
{
for (int i = 0; i < 100; i++)
{
DummyClass dc = new DummyClass();
dc.str = "Thread1";
SlotData = dc;

Thread.Sleep(100);

dc = SlotData;
Debug.Assert(dc.str == "Thread1");
}
Console.WriteLine("Thread 1 exiting...");
Interlocked.Increment(ref _finCount);
}

void ThreadProc2()
{
for (int i = 0; i < 100; i++)
{
DummyClass dc = new DummyClass();
dc.str = "Thread2";
SlotData = dc;

Thread.Sleep(100);

dc = SlotData;
Debug.Assert(dc.str == "Thread2");
}
Console.WriteLine("Thread 2 exiting...");
Interlocked.Increment(ref _finCount);
}

void ThreadProc3()
{
for (int i = 0; i < 100; i++)
{
DummyClass dc = new DummyClass();
dc.str = "Thread3";
SlotData = dc;

Thread.Sleep(100);
Debug.Assert(dc.str == "Thread3");
dc = SlotData;
}
Console.WriteLine("Thread 3 exiting...");
Interlocked.Increment(ref _finCount);
}

void ThreadProc4()
{
for (int i = 0; i < 100; i++)
{
DummyClass dc = new DummyClass();
dc.str = "Thread4";
SlotData = dc;

Thread.Sleep(100);
Debug.Assert(dc.str == "Thread4");
dc = SlotData;
}
Console.WriteLine("Thread 4 exiting...");
Interlocked.Increment(ref _finCount);
}

#region IDisposable Member

public void Dispose()
{
Console.WriteLine("freeing named slot...");
Thread.FreeNamedDataSlot("MySlot");
}

public bool IsFinished
{
get { return _finCount == 4; }
}
#endregion
}
}
 
Back
Top