We are running a simple PoC with LiteDB which is reading / writing / updating a local database concurrently.
This causes an infinite and very fast memory leak attributable to 8Kb LiteDb.Engine.PageBuffer objects.
Doing these operations sequentially rather than asynchronously does not cause a memory leak.
Does anybody have any ideas here? It doesn't follow that we would need to implement a queue for all DB operations to avoid a memory leak.
LEAKING CODE (asynchronous operations):
public class TestTable1
{
public Guid Id { get; set; }
public string Property1 { get; set; }
public int Property2 { get; set; }
}
public class Tester
{
private System.Timers.Timer _timerWrite;
private System.Timers.Timer _timerUpdate;
private System.Timers.Timer _timerRead;
private LiteDatabase _db;
public void Start()
{
var connectionString = new ConnectionString() { Filename = @"D:\temp\_db", Connection = ConnectionType.Direct, Upgrade = true };
_db = new LiteDatabase(connectionString);
_timerWrite = new System.Timers.Timer { Interval = 1000 };
_timerWrite.Elapsed += _timerWrite_Elapsed;
_timerWrite.Start();
_timerRead = new System.Timers.Timer { Interval = 500 };
_timerRead.Elapsed += _timerRead_Elapsed;
_timerRead.Start();
_timerUpdate = new System.Timers.Timer { Interval = 100 };
_timerUpdate.Elapsed += _timerUpdate_Elapsed;
_timerUpdate.Start();
}
private void _timerUpdate_Elapsed(object sender, ElapsedEventArgs e)
{
var table = _db.GetCollection<TestTable1>();
_timerUpdate.Stop();
var i = 0;
var rows = table.Find(x => x.Property2 > 25);
foreach (var row in rows)
{
row.Property1 += "updated";
table.Update(row);
i++;
}
Console.WriteLine($"Updated {i} rows from table ...");
_timerUpdate.Start();
}
private void _timerRead_Elapsed(object sender, ElapsedEventArgs e)
{
var table = _db.GetCollection<TestTable1>();
_timerRead.Stop();
var rows = table.Find(x => x.Property2 > 25);
Console.WriteLine($"Read {rows.Count()} rows from table ...");
_timerRead.Start();
}
private void _timerWrite_Elapsed(object sender, ElapsedEventArgs e)
{
var table = _db.GetCollection<TestTable1>();
_timerWrite.Stop();
for (var i = 0; i < 100; i++)
{
table.Insert(new TestTable1() { Id = Guid.NewGuid(), Property1 = i.ToString(), Property2 = i });
}
Console.WriteLine("Added 100 new rows to collection");
_timerWrite.Start();
}
public void Stop()
{
_timerUpdate.Elapsed -= _timerUpdate_Elapsed; _timerRead.Elapsed -= _timerRead_Elapsed; _timerWrite.Elapsed -= _timerWrite_Elapsed;
_timerWrite.Stop(); _timerRead.Stop(); _timerUpdate.Stop();
_timerUpdate?.Dispose(); _timerRead?.Dispose(); _timerWrite?.Dispose();
_db.Dispose();
}
}
NOT LEAKING CODE (sequential operations)
public class Tester
{
private System.Timers.Timer _timerAll;
private LiteDatabase _db;
public void Start()
{
var connectionString = new ConnectionString() { Filename = @"D:\temp\_db", Connection = ConnectionType.Direct, Upgrade = true };
_db = new LiteDatabase(connectionString);
_timerAll = new System.Timers.Timer { Interval = 100 };
_timerAll.Elapsed += _timerAll_Elapsed;
_timerAll.Start();
}
private void _timerAll_Elapsed(object sender, ElapsedEventArgs e)
{
// update
var table = _db.GetCollection<TestTable1>();
_timerAll.Stop();
var i = 0;
var rows = table.Find(x => x.Property2 > 25);
foreach (var row in rows)
{
row.Property1 += "updated";
table.Update(row);
i++;
}
Console.WriteLine($"Updated {i} rows from table ...");
// read
rows = table.Find(x => x.Property2 > 25);
// write
for (i = 0; i < 100; i++)
{
table.Insert(new TestTable1() { Id = Guid.NewGuid(), Property1 = i.ToString(), Property2 = i });
}
Console.WriteLine("Added 100 new rows to collection");
_timerAll.Start();
}
public void Stop()
{
_timerAll.Elapsed -= _timerAll_Elapsed;
_timerAll.Stop();
_timerAll?.Dispose();
}
}