Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do you have to call gc.collect explicitly sometimes with COM objects

Here is the function:

        Private Sub LoadEmail()

        Dim loSession As RDOSession = Nothing
        Dim loMessages As RDOItems = Nothing

        Try
            moNDRs = New List(Of NonDeliveryRecord)
            loSession = New Redemption.RDOSession
            loSession.LogonExchangeMailbox(MailAccountName, MailServerName)
            loMessages = loSession.GetDefaultFolder(rdoDefaultFolders.olFolderInbox).Items
            Dim Counter = 0
            For Each loMessage As RDOMail In loMessages
                Counter += 1
                moNDRs.Add(CreateNDRRecord(loMessage))
                Marshal.ReleaseComObject(loMessage)
                loMessage = Nothing
                If Counter Mod 100 = 0 Then GC.Collect()
            Next


        Finally
            If loSession IsNot Nothing Then
                loSession.Logoff()
                Marshal.FinalReleaseComObject(loSession)
                loSession = Nothing
            End If

            If loMessages IsNot Nothing Then
                Marshal.FinalReleaseComObject(loMessages)
                loMessages = Nothing
            End If

        End Try

    End Sub

The Message classes uses above are Redemption. If you look in the function above you will see:

If Counter Mod 100 = 0 Then GC.Collect()

Which is what i had to do to fix the problem we are having. I've been playing around with memory profilers this morning (ants & dottrace) to see if i could figure anything out but so far everything looks fine. I'm no low level won't who knows the ins and outs of windgb.

The error i'm getting is: Error in IMAPISession::OpenEntry: MAPI_E_TOO_BIG

The line where i always get the error is commented in the code below. I always get the error after ~450 iterations.

Is this one of the few times you have to use gc.collect is when your dealing with COM objects?

Here is the CreateNDR function with the line the error happens on:

        Public Function CreateNDRRecord(ByVal voMessage As RDOMail) As NonDeliveryRecord

        Dim loItem As RDOReportItem = Nothing
        Dim loMatches As MatchCollection = Nothing
        Dim loNonDeliveryCode As NonDeliveryRecord = New NonDeliveryRecord
        Dim lsMessage As String = String.Empty


        Try
            loNonDeliveryCode.IsBadMessage = False
            loNonDeliveryCode.MailMessageId = voMessage.EntryID

            'Debug.Print(voMessage.MessageClass.Equals("REPORT.IPM.Note.NDR").ToString())
            If voMessage.MessageClass.Equals("REPORT.IPM.Note.NDR") Then 'error always happens here
                loItem = CType(voMessage, RDOReportItem)
                If voMessage.Recipients.Count <> 0 Then
                    loNonDeliveryCode.EmailAddress = voMessage.Recipients(1).Name
                End If
                loNonDeliveryCode.IsUndeliverable = True
                lsMessage = loItem.ReportText

            ElseIf voMessage.Subject.Contains(mconSeparator) Then
                loNonDeliveryCode.EmailAddress = voMessage.Subject.Substring(voMessage.Subject.LastIndexOf(mconSeparator) + mconSeparator.Length)
                loNonDeliveryCode.ErrorCode = String.Empty
                loNonDeliveryCode.IsUndeliverable = True
                lsMessage = voMessage.Body
            End If

            If loNonDeliveryCode.IsUndeliverable Then

                loMatches = GetErrorType(lsMessage)

                If loMatches.Count > 0 Then
                    loNonDeliveryCode.ErrorCode = loMatches(loMatches.Count - 1).Value
                End If

                Dim loNDRId = GetErrorCode(loNonDeliveryCode.ErrorCode)

                If loNDRId.Count > 0 Then
                    loNonDeliveryCode.ErrorCodeId = CType(CType(loNDRId(0), DataRow).Item("NonDeliveryCodeId"), Integer)
                    loNonDeliveryCode.ErrorDescription = CType(CType(loNDRId(0), DataRow).Item("Description"), String)
                    loNonDeliveryCode.MarkAsInvalid = CType(CType(loNDRId(0), DataRow).Item("MarkAsInvalid"), Boolean)
                Else
                    If voMessage.MessageClass.Equals("REPORT.IPM.Note.NDR") Then
                        loNonDeliveryCode.ErrorCode = String.Empty
                        loNDRId = GetErrorCode(loNonDeliveryCode.ErrorCode)
                        loNonDeliveryCode.ErrorCodeId = CType(CType(loNDRId(0), DataRow).Item("NonDeliveryCodeId"), Integer)
                        loNonDeliveryCode.ErrorDescription = CType(CType(loNDRId(0), DataRow).Item("Description"), String)
                        loNonDeliveryCode.MarkAsInvalid = CType(CType(loNDRId(0), DataRow).Item("MarkAsInvalid"), Boolean)
                    Else
                        loNonDeliveryCode.ErrorCode = String.Empty
                        loNonDeliveryCode.ErrorCodeId = 1
                    End If
                End If

            End If


            Return loNonDeliveryCode

        Catch Ex As Exception
            loNonDeliveryCode.IsUndeliverable = False
            loNonDeliveryCode.IsBadMessage = True
            Return loNonDeliveryCode

        Finally
            If loItem IsNot Nothing Then
                Marshal.FinalReleaseComObject(loItem)
                loItem = Nothing
            End If

            If voMessage IsNot Nothing Then Marshal.ReleaseComObject(voMessage)

            If loMatches IsNot Nothing Then
                loMatches = Nothing
            End If

        End Try
like image 442
coding4fun Avatar asked Jan 18 '23 17:01

coding4fun


2 Answers

There are cases where a program just doesn't consume enough garbage collected memory to invoke the finalizer thread enough to clean up resources. A trouble-maker is the Thread class for example. It consumes 5 operating system handles but doesn't have a Dispose() method to release them early. And COM coclasses like the ones you are using, the managed wrapper that the CLR creates for them has a finalizer but doesn't implement IDisposable. Examples of classes where a user program just can't effectively or reliably call Dispose().

GC.Collect() was made for such cases. Also call GC.WaitForPendingFinalizers() since that's what you really want to happen.

Your usage of it is correct and defensible. You do have to tune it.

like image 176
Hans Passant Avatar answered May 14 '23 02:05

Hans Passant


What you're probably running into involves the managed heap not understanding the true memory footprint of the live COM objects.

From the .NET runtime's perspective, your COM object has the footprint of the auto-created Runtime Callable Wrapper (RCW), which is tiny. You might have instantiated a COM object that holds onto an enormous amount of memory, but that memory does not live in the .NET memory heap, so the garbage collector doesn't feel any pressure to clean it up.

Forcing the garbage collector to run in this situation seems like the right thing to do. It will dispose of any unreferenced RCWs, which will in turn drop the COM reference count on the COM objects, causing it to be freed (assuming there are no other COM references to that object, of course).

like image 31
Matt Dillard Avatar answered May 14 '23 02:05

Matt Dillard