Resolvendo vazamento de memória no IFeatureClass.Search (apenas no SDE com conexão direta) do ArcObjects?

16

O suporte da ESRI diz que eles reproduziram o problema e abriram um relatório de erro (NIM070156).

Eu determinei que há um vazamento de memória (na memória heap não gerenciada) que ocorre quando uma ferramenta no meu suplemento .NET / C # ArcMap executa uma consulta espacial (retornando um ICursor from IFeatureClass.Searchcom um ISpatialFilterfiltro de consulta). Todos os objetos COM estão sendo liberados assim que não são mais necessários (usando Marshal.FinalReleaseCOMObject).

Para determinar isso, primeiro configurei uma sessão do PerfMon com contadores para Bytes Privados, Bytes Virtuais e Conjunto de Trabalho do ArcMap.exe, e observei que os três aumentavam constantemente (em aproximadamente 500 KB por iteração) com cada uso da ferramenta que executa a consulta . Fundamentalmente, isso ocorre apenas quando executado em classes de recurso no SDE usando conexão direta (armazenamento ST_Geometry, cliente e servidor Oracle 11g). Os contadores permaneceram constantes ao usar um geodatabase de arquivo e ao conectar-se a uma instância SDE mais antiga que usa a conexão de aplicativo.

Eu então usei LeakDiag e LDGrapher (com algumas orientações desta postagem do blog ) o Windows Heap Allocator três vezes: quando carrego o ArcMap pela primeira vez e seleciono a ferramenta para inicializá-lo, depois de executá-la algumas dezenas de vezes e depois da execução mais algumas dezenas de vezes.

Aqui estão os resultados mostrados na visualização padrão do LDGrapher (tamanho total): Gráfico LDGrapher mostrando aumento constante no uso de memória

Aqui está a pilha de chamadas para a linha vermelha: Pilha de chamadas mostrando a chamada sg.dll para a função SgsShapeFindRelation2

Como você pode ver, a SgsShapeFindRelation2função em sg.dll parece ser a responsável pelo vazamento de memória.

Pelo que entendi, o sg.dll é a biblioteca de geometria básica usada pelo ArcObjects, e SgsShapeFindRelation2provavelmente é onde o filtro espacial está sendo aplicado.

Antes de fazer qualquer outra coisa, eu só queria ver se alguém havia se deparado com esse problema (ou algo semelhante) e se alguma coisa eles pudessem fazer a respeito. Além disso, qual poderia ser o motivo disso ocorrer apenas com a conexão direta? Isso soa como um bug no ArcObjects, um problema de configuração ou um problema de programação?

Aqui está uma versão mínima do método que produz esse comportamento:

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;
}

Aqui está o meu código de solução alternativa com base na discussão abaixo com o 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;
}
blah238
fonte
1
+1 ótima análise. Você está vendo apenas com uma conexão direta ?
Kirk Kuykendall
Apenas testei em um servidor antigo que usa conexão de aplicativo e não há vazamento de memória lá. Tão certo parece específico para conexão direta!
blah238
Qual versão do ArcGIS (incluindo o nível do service pack)?
Philip
Cliente: ArcGIS 10 SP2, Servidor: ArcGIS 9.3.1 SP1 (eu acho, verifique novamente amanhã).
blah238
Não há alguma versão do driver da Oracle que você precise considerar, já faz um tempo, mas talvez o ODP.NET?
Kirk Kuykendall

Respostas:

6

Isso parece um bug.

O SG contém as bibliotecas de geometria do ArcSDE e não as bibliotecas de geometria do ArcObjects ... é usado como um pré-filtro antes do teste atingir as bibliotecas de geometria do ArcObjects.

Tente o seguinte:

Omita esta linha:

pSpatialFilter.SearchOrder = esriSearchOrder.esriSearchOrderSpatial;

e como você não está salvando uma referência à linha, não há necessidade de não usar cursores de reciclagem; portanto, mude o sinalizador falso para true.

pCursor = (ICursor)pFeatureClass.Search(pSpatialFilter, true);

Você deve ver uma melhoria no consumo de memória e na velocidade de tempo de execução. No entanto, se o bug ainda for atingido, isso provavelmente o atrasará dramaticamente :)

Ragi Yaser Burhum
fonte
1
Obrigado @Ragi - Tentei as duas modificações, mas não houve alteração na taxa de memória vazando.
blah238
você pode tentar a conexão de 2 camadas (conexão direta) vs 3 camadas (servidor de aplicativos)? desde que você já tenha o servidor de aplicativos em execução, isso deve ser apenas uma alteração na cadeia de conexão sde.
Ragi Yaser Burhum
ah, acabei de ver o comentário de kirk, por isso é um problema de conexão direta. IMHO, se você fez isso com três camadas, existe a possibilidade de ver o vazamento no servidor, mas o cliente permanecerá o mesmo. Posso perguntar se você está fazendo alguma coisa com sessões de edição ou geometria de clonagem?
Ragi Yaser Burhum
1
Bem, sim e não. No modo de três camadas, o giomgr permanece residente e, para todas as conexões, gera um novo processo gsrvr que desaparece após a desconexão; portanto, se o vazamento estivesse presente, desapareceria após a desconexão. Além disso, não podemos descartar o fato de que a conexão direta tem um caminho de código ligeiramente diferente ... Tente duas coisas. Primeiro, basta desligar completamente o filtro espacial e retornar o primeiro recurso e, em seguida, tente apenas esriSpatialRelEnvelopeIntersects. Sei que semanticamente nenhum deles é o mesmo, mas queremos rastrear o vazamento primeiro.
Ragi Yaser Burhum
3
Sim, portanto, ambos os métodos estão evitando a chamada sgShapeFindRelation2. Experimente agora, esriSpatialRelEnvelopeIntersects no filtro espacial para fazer com que o sde faça uma pré-filtragem super básica e, em seguida, ITopologicalOperator :: intersect para fazer o teste real no cliente. Isso pode não ser tão eficiente quanto o sgShapeFindRelation2, mas evitará atingir essa função e, portanto, evitará o vazamento.
Ragi Yaser Burhum
4

Se alguém ainda estiver interessado nisso, ele foi corrigido na versão 10.1.

Número de suporte técnico da ESRI: NIM070156 e NIM062420

http://support.esri.com/en/bugs/nimbus/TklNMDcwMTU2 http://support.esri.com/en/bugs/nimbus/TklNMDYyNDIw

travis
fonte
Ele não lista nada para a versão corrigida, então acho que vou ter que aceitar sua palavra. Eu não testei em 10.1 embora. Além disso, o problema no meu relatório de erros não tem nada a ver com a rotulagem, portanto, não sei por que eles o marcaram como uma duplicata do outro.
blah238
1

Você pode tentar o seguinte padrão em vez de try / finally { Marshal.FinalReleaseComObject(...) }:

using (ESRI.ArcGIS.ADF.ComReleaser cr) {
    var cursor = (ICursor) fc.Search(...);
    cr.ManageLifetime(cursor);
    // ...
}

Além disso, trabalhar com a Direct Connect, eu tive algum sucesso em loops, forçando System.GC.Collect()periodicamente (a cada tão muitas iterações), no entanto desagradável que parece.

David Holmes
fonte
Dei uma chance à abordagem ComReleaser usando o método de James MacKay para reciclagem de cursores descrito aqui: forums.arcgis.com/threads/… - não fez nenhuma diferença (apenas envolve Marshal.ReleaseCOMObject de qualquer maneira, portanto, não é muito surpreendente). Eu também tentei usar GC.Collect, mas também não teve efeito. Eu deveria ter mencionado que também examinei a memória gerenciada usando alguns perfis do .NET e nenhum deles encontrou objetos gerenciados ou a memória gerenciada acumulando, o que me levou a analisar a memória não gerenciada.
blah238