Résolution des fuites de mémoire dans IFeatureClass.Search (uniquement sur SDE avec connexion directe) d'ArcObjects?

16

Le support ESRI déclare avoir reproduit le problème et ouvert un rapport de bogue (NIM070156).

J'ai déterminé qu'il ya une fuite de mémoire (en mémoire de tas non géré) qui se produit lorsqu'un outil dans mon .NET / C # ArcMap add-in effectue une requête spatiale (un retour ICursorde IFeatureClass.Searchavec un ISpatialFilterfiltre de requête). Tous les objets COM sont libérés dès qu'ils ne sont plus nécessaires (à l'aide Marshal.FinalReleaseCOMObject).

Pour déterminer cela, j'ai d'abord configuré une session PerfMon avec des compteurs pour les octets privés, les octets virtuels et le jeu de travail d'ArcMap.exe, et j'ai noté que les trois augmentaient régulièrement (d'environ 500 Ko par itération) à chaque utilisation de l'outil qui exécute la requête . Surtout, cela ne se produit que lorsqu'il est exécuté contre des classes d' entités sur SDE à l'aide d'une connexion directe (stockage ST_Geometry, client et serveur Oracle 11g). Les compteurs sont restés constants lors de l'utilisation d'une géodatabase fichier, ainsi que lors de la connexion à une instance SDE plus ancienne qui utilise la connexion d'application.

J'ai ensuite utilisé LeakDiag et LDGrapher (avec quelques conseils de ce billet de blog ) et j'ai enregistré l'allocateur de tas de Windows à trois reprises: lorsque je charge ArcMap pour la première fois et sélectionne l'outil pour l'initialiser, après avoir exécuté l'outil quelques dizaines de fois et après avoir exécuté quelques dizaines de fois de plus.

Voici les résultats présentés dans la vue par défaut de LDGrapher (taille totale): Graphique LDGrapher montrant une augmentation constante de l'utilisation de la mémoire

Voici la pile d'appels pour la ligne rouge: Pile d'appels affichant l'appel sg.dll à la fonction SgsShapeFindRelation2

Comme vous pouvez le voir, la SgsShapeFindRelation2fonction dans sg.dll semble être responsable de la fuite de mémoire.

Si je comprends bien, sg.dll est la bibliothèque de géométrie de base utilisée par ArcObjects, et SgsShapeFindRelation2est probablement là où le filtre spatial est appliqué.

Avant de faire quoi que ce soit d'autre, je voulais juste voir si quelqu'un d'autre avait rencontré ce problème (ou quelque chose de similaire) et si tout ce qu'ils pouvaient faire à ce sujet. De plus, quelle pourrait être la raison pour laquelle cela ne se produit qu'avec une connexion directe? Cela ressemble-t-il à un bogue dans ArcObjects, à un problème de configuration ou à un problème de programmation?

Voici une version de travail minimale de la méthode qui produit ce comportement:

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

Voici mon code de contournement basé sur la discussion ci-dessous avec 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
la source
1
+1 grande analyse. Le voyez-vous uniquement avec une connexion directe ?
Kirk Kuykendall
Je viens de le tester sur un serveur plus ancien qui utilise la connexion d'application et il n'y a aucune fuite de mémoire. Il semble donc spécifique de se connecter directement!
blah238
Quelle version d'ArcGIS (y compris au niveau du Service Pack)?
Philip
Client: ArcGIS 10 SP2, serveur: ArcGIS 9.3.1 SP1 (je pense, revérifier demain).
blah238
N'y a-t-il pas une version du pilote Oracle que vous devez considérer, cela fait longtemps, mais peut-être ODP.NET?
Kirk Kuykendall

Réponses:

6

Cela ressemble à un bug.

SG contient les bibliothèques de géométrie ArcSDE et non les bibliothèques de géométrie ArcObjects ... il est utilisé comme un pré-filtre avant que le test ne frappe les bibliothèques de géométrie ArcObjects.

Essaye ça:

Omettez cette ligne:

pSpatialFilter.SearchOrder = esriSearchOrder.esriSearchOrderSpatial;

et puisque vous n'enregistrez pas une référence à la ligne, il n'est pas nécessaire que vous n'utilisiez pas de curseurs de recyclage, donc basculez le faux indicateur sur true.

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

Vous devriez voir une amélioration à la fois de la consommation de mémoire et de la vitesse d'exécution. Néanmoins, si le bogue est toujours rencontré, cela le retardera, espérons-le, de façon spectaculaire :)

Ragi Yaser Burhum
la source
1
Merci @Ragi - J'ai essayé les deux modifications mais il n'y a eu aucun changement dans le taux de fuite de mémoire.
blah238
pouvez-vous essayer une connexion à 2 niveaux (connexion directe) vs 3 niveaux (serveur d'applications)? à condition que le serveur d'applications soit déjà en cours d'exécution, cela ne devrait être qu'un changement dans la chaîne de connexion sde.
Ragi Yaser Burhum
oh, je viens de voir le commentaire de kirk, donc c'est un problème de connexion directe. À mon humble avis, si vous avez fait cela avec 3 niveaux, il est possible que vous voyiez la fuite côté serveur, mais le client restera le même. Puis-je vous demander si vous faites quoi que ce soit avec des sessions d'édition ou des géométries de clonage?
Ragi Yaser Burhum
1
Eh bien, oui et non. Le mode 3 niveaux, le giomgr reste résident et pour chaque connexion, il engendre un nouveau processus gsrvr qui mourra après votre déconnexion, donc si la fuite était là, elle disparaîtrait après votre déconnexion. De plus, nous ne pouvons pas ignorer le fait que la connexion directe a un chemin de code très légèrement différent ... Essayez deux choses. Premièrement, désactivez complètement le filtre spatial et renvoyez la première fonctionnalité, puis essayez simplement esriSpatialRelEnvelopeIntersects. Je sais que sémantiquement aucun de ces éléments n'est le même, mais nous voulons d'abord suivre la fuite.
Ragi Yaser Burhum
3
Oui, les deux méthodes évitent donc l'appel sgShapeFindRelation2. Essayez ceci maintenant, esriSpatialRelEnvelopeIntersects sur le filtre spatial pour que sde fasse un pré-filtrage super basique, puis ITopologicalOperator :: intersect pour faire le test réel sur le client. Cela peut ne pas être aussi efficace que sgShapeFindRelation2, mais cela évitera de frapper cette fonction et donc d'éviter la fuite.
Ragi Yaser Burhum
4

Si quelqu'un est toujours intéressé par cela, il a été corrigé dans la version 10.1.

Numéro de support technique ESRI: NIM070156 et NIM062420

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

travis
la source
Il ne répertorie rien pour la version fixe, donc je suppose que je vais devoir vous croire sur parole. Je n'ai pas testé à 10.1 cependant. De plus, le problème dans mon rapport de bogue n'a rien à voir avec l'étiquetage, donc je ne sais pas pourquoi ils l'ont marqué comme doublon de cet autre.
blah238
1

Vous pouvez essayer le modèle suivant au lieu de try / finally { Marshal.FinalReleaseComObject(...) }:

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

Travaillant également avec Direct Connect, j'ai eu un certain succès dans les boucles en forçant System.GC.Collect()périodiquement (toutes les nombreuses itérations), aussi méchant que cela puisse paraître.

David Holmes
la source
J'ai donné une approche à ComReleaser en utilisant la méthode de James MacKay pour recycler les curseurs décrite ici: forums.arcgis.com/threads/… - cela n'a fait aucune différence (cela enveloppe juste Marshal.ReleaseCOMObject de toute façon, donc pas trop surprenant). J'ai également essayé d'utiliser GC.Collect mais cela n'a eu aucun effet. J'aurais dû mentionner que j'ai également examiné la mémoire gérée à l'aide de deux profileurs .NET et qu'aucun d'entre eux n'a trouvé d'objets gérés ou de mémoire gérée s'empilant, ce qui m'a amené à regarder la mémoire non gérée.
blah238