Interface Reference Counting and Object Lifetime Management in Object Pascal
Author: ikelaiah
Issue: Access Violation Due to Inappropriate Object Freeing
Problem Description#
When passing an object to a method that expects an interface parameter, the object's reference count is increased. Manually freeing the object while this interface reference exists leads to access violations. This is a common pitfall when mixing manual object management with interface reference counting in Object Pascal.
Example of Problematic Code#
procedure TProducerConsumerThreadPool.Queue(AProcedure: TThreadProcedure);
var
WorkItem: TProducerConsumerWorkItem;
begin
WorkItem := TProducerConsumerWorkItem.Create(Self);
try
WorkItem.FProcedure := AProcedure;
WorkItem.FItemType := witProcedure;
TryQueueWorkItem(WorkItem); // Implicitly converts to IWorkItem, increasing ref count
except
on E:Exception do
begin
WorkItem.Free; // WRONG: Manual Free while interface reference exists
raise;
end;
end;
end;
// The method receiving the interface parameter
function TryQueueWorkItem(WorkItem: IWorkItem): Boolean; // Takes interface parameterWhat goes wrong
WorkItemobject is created- Object is passed to
TryQueueWorkItem, which expects anIWorkIteminterface - Implicit conversion to interface increases reference count
- Exception occurs
- Exception handler manually frees the object
- Interface reference still exists with non-zero reference count
- When interface goes out of scope, it tries to release already-freed object
- Access violation occurs
Correct Implementation#
procedure TProducerConsumerThreadPool.Queue(AProcedure: TThreadProcedure);
var
WorkItem: TProducerConsumerWorkItem;
WorkItemIntf: IWorkItem;
begin
WorkItem := TProducerConsumerWorkItem.Create(Self);
WorkItem.FProcedure := AProcedure;
WorkItem.FItemType := witProcedure;
WorkItemIntf := WorkItem; // Explicit interface assignment for clarity
try
TryQueueWorkItem(WorkItemIntf);
except
on E: Exception do
begin
DebugLog('TProducerConsumerThreadPool.Queue: Exception caught: ' + E.Message);
raise; // No manual Free - let interface handle cleanup
end;
end;
end;Best Practices#
- Be aware of implicit interface conversions - When passing an object to a method expecting an interface - When assigning an object to an interface variable
- Never manually free objects that have active interface references
- Make interface usage explicit for better code clarity
- Use separate variables when you need both object and interface access
- Let interface reference counting handle cleanup
Detection#
This issue can be detected through:
- Unit tests that trigger exceptions during interface operations
- Access violations occurring after exception handling
- Memory corruption in complex scenarios
- Heap corruption reports
Example Test Case That Caught This Issue#
procedure TTestProducerConsumerThreadPool.Test08_QueueFullBehavior;
const
QUEUE_SIZE = 2;
THREAD_COUNT = 1;
var
TestPool: TProducerConsumerThreadPool;
ExceptionRaised: Boolean;
begin
TestPool := TProducerConsumerThreadPool.Create(THREAD_COUNT, QUEUE_SIZE);
try
// Fill queue to capacity with long-running tasks so the worker cannot
// drain the queue before the third enqueue is attempted.
TestPool.Queue(@LongTask);
TestPool.Queue(@LongTask);
// This should trigger the exception
try
TestPool.Queue(@LongTask); // Queue is full, will raise EQueueFullException
except
on E: EQueueFullException do
ExceptionRaised := True;
end;
AssertTrue('Should have raised queue full exception', ExceptionRaised);
finally
TestPool.Free;
end;
end;Prevention Strategies#
Design Phase#
- Clear documentation of interface parameter expectations
- Consistent object lifetime management strategy
Implementation Phase#
- Make interface conversions explicit
- Avoid manual object freeing when interfaces are involved
- Use clear variable naming to indicate interface usage
Testing Phase#
- Test exception paths thoroughly
- Include full queue conditions in tests
- Monitor for memory leaks and access violations
Conclusion#
Understanding when implicit interface conversions occur and their impact on reference counting is crucial for robust Object Pascal applications. The key is to recognize that once an object is referenced by an interface, its lifetime should be managed by the interface reference counting mechanism, not manual freeing.
---
Note: This document is based on real-world experience with a thread pool implementation (ThreadPool.ProducerConsumer) in Object Pascal/Free Pascal.