I have noticed, that the MiniProfiler somehow doesn't dispose/release the Timings leading to a possible memory leak.
I used the DotMemory of Jetbrains to make a snapshot before and after the workload. All of the 1000 Timings still remained in an instance of the MiniProfiler, which i cannot seem to get rid of.
This could lead quite some problems of, for example, someone would profile his database queries which could have quite some length to them.
Minimal-example:
public static void Main()
{
try
{
//Snapshot here
Console.ReadKey();
var mp = MiniProfiler.StartNew();
var rnd = new Random();
for (int j = 0; j < 1000; j++)
{
using (mp.Step("outer"))
{
rnd.Next();
}
}
Console.WriteLine(MiniProfiler.Current.RenderPlainText());
mp.Stop(true);
mp = null;
if (Debugger.IsAttached)
Console.ReadKey();
//Snapshot here
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
Thanks for any help!
Edited my question for one more example. Thanks @Ed Pavlov, but as soon as I create a bigger example the Timings will still remain. Next example was build with Release Config and executes a sql query against a database. (I deliberately choose a quite big command text to demonstrate the phenomenon)
public static async Task Main()
{
try
{
Console.ReadLine();
await DoStuff();
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
internal static async Task DoStuff()
{
var mp = MiniProfiler.StartNew("Test");
for (int i = 0; i < 5000; i++)
{
await Test();
if (i % 200 == 0) Console.WriteLine(i);
}
Console.WriteLine(MiniProfiler.Current?.RenderPlainText());
await mp.StopAsync(true);
}
public static async Task Test()
{
var rnd = new Random();
var txt = CommandFacade.CommandText.Replace("@@ID@@", rnd.Next(3000).ToString());
using (MiniProfiler.Current.Step("Level 1"))
using (var conn = GetConnection())
using (var cmd = new ProfiledDbCommand(new SqlCommand(txt), conn, null))
{
await conn.OpenAsync();
await cmd.ExecuteNonQueryAsync();
conn.Close();
}
}
public static DbConnection GetConnection()
{
DbConnection cnn = new SqlConnection(ConnectionString);
if (MiniProfiler.Current != null)
{
cnn = new ProfiledDbConnection(cnn, MiniProfiler.Current);
}
return cnn;
}
Picture of dotMemory with new example:
DotMemory Overview Screenshot
Screenshot of retained object
Despite you set the variable "mp" to null when your application is built w/o optimization in the "Debug" configuration, compiler generates code which keeps all objects till the end of the method. As you can see on the screenshot both, MiniProfiler and all instances of Timing are held in memory by local variables.
Build your application in the "Release" configuration, or better extract the code into a separate method to imitate you real code better and you will see that there are no these objects in the memory after the method call.