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
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With