ESRI Support say they have reproduced the issue and have opened a bug report (NIM070156).
I have determined that there is a memory leak (in unmanaged heap memory) that occurs when a tool in my .NET/C# ArcMap add-in performs a spatial query (returning an ICursor
from IFeatureClass.Search
with an ISpatialFilter
query filter). All COM objects are being released as soon as they are no longer needed (using Marshal.FinalReleaseCOMObject
).
To determine this I first set up a PerfMon session with counters for ArcMap.exe's Private Bytes, Virtual Bytes and Working Set, and noted that all three steadily increased (by roughly 500KB per iteration) with each usage of the tool that that performs the query. Crucially, this only occurs when performed against feature classes on SDE using direct connect (ST_Geometry storage, Oracle 11g client and server). The counters remained constant when using a file geodatabase, as well as when connecting to an older SDE instance that uses application connect.
I then used LeakDiag and LDGrapher (with some guidance from this blog post) and logged the Windows Heap Allocator at three times: when I first load ArcMap and select the tool to initialize it, after running the tool a couple dozen times, and after running it a few more dozen times.
Here are the results as shown in LDGrapher's default view (total size):
Here is the call stack for the red line:
As you can see the SgsShapeFindRelation2
function in sg.dll appears to be what is responsible for the memory leak.
As I understand sg.dll is the core geometry library used by ArcObjects, and SgsShapeFindRelation2
is presumably where the spatial filter is being applied.
Before I do anything else, I just wanted to see if anyone else had run into this issue (or something similar) and what if anything they were able to do about it. Also what could be the reason for this occurring only with direct connect? Does this sound like a bug in ArcObjects, a configuration issue, or a programming problem?
Here is a minimal working version of the method that produces this behavior:
private string GetValueAtPoint(IPoint pPoint, IFeatureClass pFeatureClass, string pFieldName)
{
string results = "";
ISpatialFilter pSpatialFilter = null;
ICursor pCursor = null;
IRow pRow = null;
try
{
pSpatialFilter = new SpatialFilterClass();
pSpatialFilter.Geometry = pPoint;
pSpatialFilter.GeometryField = pFeatureClass.ShapeFieldName;
pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
pSpatialFilter.SearchOrder = esriSearchOrder.esriSearchOrderSpatial;
pCursor = (ICursor)pFeatureClass.Search(pSpatialFilter, false);
pRow = pCursor.NextRow();
if (pRow != null)
results = pRow.get_Value(pFeatureClass.FindField(pFieldName)).ToString();
}
finally
{
// Explicitly release COM objects
if (pRow != null)
Marshal.FinalReleaseComObject(pRow);
if (pCursor != null)
Marshal.FinalReleaseComObject(pCursor);
if (pSpatialFilter != null)
Marshal.FinalReleaseComObject(pSpatialFilter);
}
return results;
}
Here's my workaround code based on the discussion below with Ragi:
private bool PointIntersectsFeature(IPoint pPoint, IFeature pFeature)
{
bool returnVal = false;
ITopologicalOperator pTopoOp = null;
IGeometry pGeom = null;
try
{
pTopoOp = ((IClone)pPoint).Clone() as ITopologicalOperator;
if (pTopoOp != null)
{
pGeom = pTopoOp.Intersect(pFeature.Shape, esriGeometryDimension.esriGeometry0Dimension);
if (pGeom != null && !(pGeom.IsEmpty))
returnVal = true;
}
}
finally
{
// Explicitly release COM objects
if (pGeom != null)
Marshal.FinalReleaseComObject(pGeom);
if (pTopoOp != null)
Marshal.FinalReleaseComObject(pTopoOp);
}
return returnVal;
}
private string GetValueAtPoint(IPoint pPoint, IFeatureClass pFeatureClass, string pFieldName)
{
string results = "";
ISpatialFilter pSpatialFilter = null;
IFeatureCursor pFeatureCursor = null;
IFeature pFeature = null;
try
{
pSpatialFilter = new SpatialFilterClass();
pSpatialFilter.Geometry = pPoint;
pSpatialFilter.GeometryField = pFeatureClass.ShapeFieldName;
pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelEnvelopeIntersects;
pFeatureCursor = pFeatureClass.Search(pSpatialFilter, true);
pFeature = pFeatureCursor.NextFeature();
while (pFeature != null)
{
if (PointIntersectsFeature(pPoint, pFeature))
{
results = pFeature.get_Value(pFeatureClass.FindField(pFieldName)).ToString();
break;
}
pFeature = pFeatureCursor.NextFeature();
}
}
finally
{
// Explicitly release COM objects
if (pFeature != null)
Marshal.FinalReleaseComObject(pFeature);
if (pFeatureCursor != null)
Marshal.FinalReleaseComObject(pFeatureCursor);
if (pSpatialFilter != null)
Marshal.FinalReleaseComObject(pSpatialFilter);
}
return results;
}
Answer
This looks like a bug.
SG contains the ArcSDE geometry libraries and not the ArcObjects geometry libraries... it is used as a pre-filter before the test hits the ArcObjects geometry libraries.
Try this:
Omit this line:
pSpatialFilter.SearchOrder = esriSearchOrder.esriSearchOrderSpatial;
and since you are not saving a reference to the row, there is no need for you not to use recycling cursors, so switch the false flag to true.
pCursor = (ICursor)pFeatureClass.Search(pSpatialFilter, true);
You should see an improvement both in memory consumption and runtime speed. Nevertheless, if the bug is still hit, this will hopefully dramatically delay it :)
No comments:
Post a Comment