AnswerStopping the leaks
Originally posted at 4/19/2011
Yesterday I posted the following challenge:
Given the following API, can you think of a way that would prevent memory leaks?
public interface IBufferPool { byte[] TakeBuffer(int size); void ReturnBuffer(byte[] buffer); }
The problem with having something like this is that forgetting to return the buffer is going to cause a memory leak. Instead of having that I would like to have the application stop if a buffer is leaked. Leaked means that no one is referencing this buffer but it wasn’t returned to the pool.
What I would really like is that when running in debug mode, leaking a buffer would stop the entire application and tell me:
- That a buffer was leaked.
- What was the stack trace that allocated that buffer.
Let us take a look at how we are going about implementing this, shall we? I am going to defer the actual implementation of the buffer pool to System.ServiceModel.Channels.BufferManager and focus on providing the anti leak features. The result is that this code:
IBufferPool pool = new BufferPool(1024*512, 1024); var buffer = pool.TakeBuffer(512); GC.WaitForPendingFinalizers(); // nothing here pool.ReturnBuffer(buffer); buffer = null; GC.WaitForPendingFinalizers(); // nothing here, we released the memory properly pool.TakeBuffer(512); // take and discard a buffer without returning to the pool GC.WaitForPendingFinalizers(); // failure!
Will result in the following error:
Unhandled Exception: System.InvalidOperationException: A buffer was leaked. Initial allocation: at ConsoleApplication1.BufferPool.BufferTracker.TrackAllocation() in IBufferPool.cs:line 22 at ConsoleApplication1.BufferPool.TakeBuffer(Int32 size) in IBufferPool.cs:line 60 at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 21
And now for the implementation:
public class BufferPool : IBufferPool { public class BufferTracker { private StackTrace stackTrace; public void TrackAllocation() { stackTrace = new StackTrace(true); GC.ReRegisterForFinalize(this); } public void Discard() { stackTrace = null; GC.SuppressFinalize(this); } ~BufferTracker() { if (stackTrace == null) return; throw new InvalidOperationException( "A buffer was leaked. Initial allocation:" + Environment.NewLine + stackTrace ); } } private readonly BufferManager bufferManager; private ConditionalWeakTable<byte[], BufferTracker> trackLeakedBuffers = new ConditionalWeakTable<byte[], BufferTracker>(); public BufferPool(long maxBufferPoolSize, int maxBufferSize) { bufferManager = BufferManager.CreateBufferManager(maxBufferPoolSize, maxBufferSize); } public void Dispose() { bufferManager.Clear(); // note that disposing the pool before returning all of the buffers will cause a crash } public byte[] TakeBuffer(int size) { var buffer = bufferManager.TakeBuffer(size); trackLeakedBuffers.GetOrCreateValue(buffer).TrackAllocation(); return buffer; } public void ReturnBuffer(byte[] buffer) { BufferTracker value; if(trackLeakedBuffers.TryGetValue(buffer, out value)) { value.Discard(); } bufferManager.ReturnBuffer(buffer); } }
As you can see, utilizing ConditionalWeakTable is quite powerful, since it allows us to support a lot of really advanced scenarios in a fairly simple ways.
More posts in "Answer" series:
- (22 Jan 2025) What does this code do?
- (05 Jan 2023) what does this code print?
- (15 Dec 2022) What does this code print?
- (07 Apr 2022) Why is this code broken?
- (20 Jan 2017) What does this code do?
- (16 Aug 2011) Modifying execution approaches
- (30 Apr 2011) Stopping the leaks
- (24 Dec 2010) This code should never hit production
- (21 Dec 2010) Your own ThreadLocal
- (11 Feb 2010) Debugging a resource leak
- (03 Sep 2009) The lazy loaded inheritance many to one association OR/M conundrum
- (04 Sep 2008) Don't stop with the first DSL abstraction
- (12 Jun 2008) How many tests?

Comments
Comment preview