Rozwiązanie problemu wycieku pamięci w IFeatureClass.Search (tylko w SDE z bezpośrednim połączeniem) ArcObjects?

16

Obsługa ESRI twierdzi, że odtworzyła problem i otworzyła raport o błędzie (NIM070156).

Stwierdziłem, że występuje przeciek pamięci (w niezarządzanej pamięci sterty), który występuje, gdy narzędzie w moim dodatku .NET / C # ArcMap wykonuje zapytanie przestrzenne (zwracając ICursorz IFeatureClass.Searchz ISpatialFilterfiltrem zapytań). Wszystkie obiekty COM są zwalniane, gdy tylko nie są już potrzebne (za pomocą Marshal.FinalReleaseCOMObject).

Aby to ustalić, najpierw skonfigurowałem sesję PerfMon z licznikami dla prywatnych bajtów, wirtualnych bajtów i zestawu roboczego ArcMap.exe i zauważyłem, że wszystkie trzy stale rosną (o około 500 KB na iterację) przy każdym użyciu narzędzia wykonującego zapytanie . Co najważniejsze, dzieje się tak tylko wtedy, gdy jest przeprowadzane w odniesieniu do klas elementów w SDE przy użyciu bezpośredniego połączenia (pamięć ST_Geometry, klient i serwer Oracle 11g) Liczniki pozostały stałe podczas korzystania z geobazy danych pliku, a także podczas łączenia się ze starszą instancją SDE korzystającą z połączenia aplikacji.

Następnie użyłem LeakDiag i LDGrapher (z pewnymi wskazówkami z tego postu na blogu ) i trzykrotnie zalogowałem Windows Heap Allocator: kiedy po raz pierwszy ładuję ArcMap i wybieram narzędzie do jego inicjalizacji, po kilkadziesiątowym uruchomieniu narzędzia i po uruchomieniu to jeszcze kilkadziesiąt razy.

Oto wyniki pokazane w domyślnym widoku LDGrapher (całkowity rozmiar): Wykres LDGrapher pokazujący stały wzrost zużycia pamięci

Oto stos wywołań dla czerwonej linii: Stos wywołań pokazujący wywołanie sg.dll do funkcji SgsShapeFindRelation2

Jak widać, SgsShapeFindRelation2funkcja w sg.dll wydaje się być odpowiedzialna za wyciek pamięci.

Jak rozumiem, sg.dll to podstawowa biblioteka geometrii używana przez ArcObjects i SgsShapeFindRelation2prawdopodobnie jest tam, gdzie stosowany jest filtr przestrzenny.

Zanim zrobię cokolwiek innego, chciałem tylko sprawdzić, czy ktokolwiek napotkał ten problem (lub coś podobnego) i co, jeśli mógł coś z tym zrobić. Co może być przyczyną takiego stanu rzeczy tylko w przypadku bezpośredniego połączenia? Czy to brzmi jak błąd w ArcObjects, problem z konfiguracją lub problem z programowaniem?

Oto minimalna działająca wersja metody, która powoduje takie zachowanie:

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

Oto mój kod obejścia oparty na poniższej dyskusji z 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
źródło
1
+1 świetna analiza. Czy widzisz to tylko z bezpośrednim połączeniem ?
Kirk Kuykendall
Właśnie przetestowałem to na starszym serwerze, który używa połączenia z aplikacją i nie ma tam przecieku pamięci. Więc na pewno wydaje się specyficzne dla bezpośredniego połączenia!
blah238
Jaka wersja ArcGIS (w tym poziom dodatku Service Pack)?
Philip
Klient: ArcGIS 10 SP2, Serwer: ArcGIS 9.3.1 SP1 (myślę, że sprawdzi jutro dwukrotnie).
blah238
Czy nie jest jakaś wersja sterownika Oracle, którą należy wziąć pod uwagę, minęło trochę czasu, ale może ODP.NET?
Kirk Kuykendall

Odpowiedzi:

6

To wygląda na błąd.

SG zawiera biblioteki geometrii ArcSDE, a nie biblioteki geometrii ArcObjects ... jest on używany jako filtr wstępny, zanim test trafi do bibliotek geometrii ArcObjects.

Spróbuj tego:

Pomiń ten wiersz:

pSpatialFilter.SearchOrder = esriSearchOrder.esriSearchOrderSpatial;

a ponieważ nie zapisujesz odwołania do wiersza, nie musisz używać kursorów recyklingu, więc przełącz fałszywą flagę na wartość true.

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

Powinieneś zauważyć poprawę zarówno zużycia pamięci, jak i szybkości działania. Niemniej jednak, jeśli błąd zostanie nadal trafiony, mam nadzieję, że dramatycznie go opóźni :)

Ragi Yaser Burhum
źródło
1
Dzięki @Ragi - próbowałem obu modyfikacji, ale nie było zmian w szybkości wycieku pamięci.
blah238,
czy możesz wypróbować połączenie 2-warstwowe (bezpośrednie połączenie) vs 3-warstwowe (serwer aplikacji)? pod warunkiem, że masz już uruchomiony serwer aplikacji, powinna to być tylko zmiana ciągu połączenia sde.
Ragi Yaser Burhum,
och, właśnie zobaczyłem komentarz Kirka, więc jest to problem z bezpośrednim połączeniem. IMHO, jeśli zrobiłeś to z 3 poziomami, istnieje możliwość, że zobaczysz wyciek po stronie serwera, ale klient nie zmieni się. Czy mogę zapytać, czy robisz coś z edycjami lub klonowaniem geometrii?
Ragi Yaser Burhum
1
Cóż, tak i nie. W trybie 3-poziomowym giomgr pozostaje rezydentem i dla każdego połączenia tworzy nowy proces gsrvr, który umrze po twoim rozłączeniu, więc jeśli wyciek miał miejsce, zniknie po rozłączeniu. Nie możemy też lekceważyć faktu, że bezpośrednie połączenie ma nieco inną ścieżkę kodu ... Spróbuj dwóch rzeczy. Po pierwsze, całkowicie wyłącz filtr przestrzenny i zwróć pierwszą funkcję, a następnie spróbuj po prostu esriSpatialRelEnvelopeIntersects. Wiem, że semantycznie żaden z nich nie jest taki sam, ale najpierw chcemy śledzić wyciek.
Ragi Yaser Burhum
3
Tak, więc obie metody unikają wywołania sgShapeFindRelation2. Spróbuj teraz, esriSpatialRelEnvelopeIntersects na filtrze przestrzennym, aby sde wykonało super podstawowe filtrowanie wstępne, a następnie ITopologicalOperator :: intersect, aby wykonać właściwy test na kliencie. Może to nie być tak skuteczne jak sgShapeFindRelation2, ale pozwoli uniknąć użycia tej funkcji, a tym samym uniknąć wycieku.
Ragi Yaser Burhum
4

Jeśli ktoś nadal jest tym zainteresowany, zostało to naprawione w wersji 10.1.

Numer pomocy technicznej ESRI: NIM070156 i NIM062420

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

Travis
źródło
Nie ma w nim nic na temat wersji naprawionej, więc myślę, że muszę po prostu uwierzyć ci na słowo. Jednak nie testowałem w 10.1. Również problem w moim zgłoszeniu błędu nie ma nic wspólnego z etykietowaniem, więc nie jestem pewien, dlaczego oznaczyli go jako duplikat tego drugiego.
blah238,
1

Możesz wypróbować następujący wzór zamiast try / finally { Marshal.FinalReleaseComObject(...) }:

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

Pracując również z Direct Connect, odniosłem pewien sukces w pętlach, wymuszając System.GC.Collect()okresowo (co tak wiele iteracji), jakkolwiek to wygląda paskudnie.

David Holmes
źródło
Poddałem podejście ComReleaser, używając metody Jamesa MacKaya do recyklingu kursorów opisanych tutaj: forums.arcgis.com/threads/… - to nie miało znaczenia (po prostu otacza Marshal.ReleaseCOMObject, więc nie jest to zbyt zaskakujące). Próbowałem także użyć GC.Collect, ale nie przyniosło to żadnego efektu. Powinienem wspomnieć, że patrzyłem również na pamięć zarządzaną za pomocą kilku profilerów .NET i żaden z nich nie znalazł żadnych zarządzanych obiektów ani pamięci zarządzanej, które gromadzą się, co prowadzi mnie do spojrzenia na pamięć niezarządzaną.
blah238