How to recover from loss of connection to a (locally hosted) DCOM object? - vb.net

(I'm not sure if this is would be better suited for code review)
I'm using a (closed source) library that maintains a DCOM connection internally, this DCOM connection goes to a service running on the local machine. This service is stopped and started quite frequently by the user (not through our application).
Unfortunately it looks like DCOM related exceptions bubble up to our application.
There doesn't seem to be any way given in the library to determine if the connection to the service is still valid, right now the only way to know we've lost connection to the service to try to do something and have it throw an exception (the type of which varies significantly).
My best attempt to try to make this a little more manageable is to separate out exceptions that are usually caused by DCOM from exceptions that are caused by configuration problems....
Here's a sample of what the code ended up looking like:
Private Sub Initialize()
Dim ParametersToDComObject As Integer
Try
Dim HRESULT = ClassThatUsesDCOMInstance.InitDCOMObject(ParametersToDComObject)
Console.WriteLine("Connected to service through DCOM.")
Catch Ex As Exception
If (IsLikelyCausedByConnectionLoss(Ex)) Then
'in this case, throw to make sure that we don't try to continue assuming that the connection went through
'I'm not sure what type of exception would be best to throw here,
'this would occur if the service is not running; the calling function
'would notify the user that there's a problem
Throw New InvalidOperationException(Ex.Message, Ex)
Else
Throw
End If
End Try
End Sub
Private Sub ReadDataFromClass(EntryName As String)
Try
Dim HRESULT As Integer = ClassThatUsesDCOMInstance.ReadEntry(EntryName)
Catch Ex As Exception
If (IsLikelyCausedByConnectionLoss(Ex)) Then
'If the service is stopped, from the application's perspective every method in
'this class suddenly starts failing, and it won't start working again until a new instance
'of the class that uses DCOM is made.
'
'So the sub below attempts to make sure that our application knows that the methods in this class
'won't work anymore... (I usually set ClassThatUsesDCOMInstance to Nothing after cleaning up)
'
'Sometimes I just have to "reconnect", but the tricky thing is, the only recovery option may be to
'(re)start the service that's providing the DCOM object; it seems best to notify the
'user in this case since in most cases the user knowlingly stopped the service, it's
'normal to stop the service to configure it
CleanupAfterConnectionLoss()
Throw New InvalidOperationException(Ex.Message, Ex)
End If
If (TypeOf (Ex) Is TheLibraryDefinedThisException) Then
'let's say that the library throws this if we try to read an entry that doesn't exist (e.g., if the configuration has changed)
'we can't fix the problem automatically, but we can notify the user that something has changed external to this program, so they can
'either fix the service or edit their configuration for our application
Console.WriteLine("Entry " & EntryName & " could not be read " & Ex.Message & " check that the entry is defined on the service")
Else
Throw
End If
End Try
End Sub
Private Function IsLikelyCausedByConnectionLoss(ThrownException As Exception) As Boolean
'I'm thinking it's best to put this in a separate function because development has gone like this:
'
' 1. the provided documentation does not give any information about what methods will throw what kinds of exceptions
' All functions return **HRESULTs**, I assumed that there weren't going to be /any/ exceptions
'
' It turns out the HRESULTs cover certain errors and exceptions cover certain errors, with some overlap...
'
' 2. I determined experimentally during testing that it throws a <Library Defined Exception> if I give it an invalid entry name, okay, I can work with that
'
' 3. I determined experimentally that if I stop the service while we're connected, all functions throw a COMException, not convenient but ok.
'
' 4. Weeks later, I figure out that it also occasionally throws InvalidComObjectException if the service is stopped under the same situation
'
' 5. Weeks later, I find out that it occasionally throws ApplicationExceptions under the same situation
'
' 6. Weeks later, I find out that certain functions will additionally throw an InvalidCastException
'
' note that there's no change in input to the library to cause these different exceptions, and the only cause
' is the loss of connection to the DCOM server
'
' in the last code setup I had to manually update every try/catch block that had anything to do with this library every time
' I found something new...
If (TypeOf (ThrownException) Is InvalidCastException) Then
'I really don't like catching this, but I get this if the COM object fails to bind to the interface,
'which occasionally happens if the service isn't there or isn't set up right
Return True
ElseIf (TypeOf (ThrownException) Is ApplicationException) Then
'I really don't like catching this either, same reason as above
Return True
'I don't believe this is an exhaustive list of every exception that can be thrown due to loss of DCOM connection,
'please let me know if you know of more exceptions that can be thrown...
ElseIf (TypeOf (ThrownException) Is Runtime.InteropServices.COMException) Then
Return True
ElseIf (TypeOf (ThrownException) Is Runtime.InteropServices.InvalidComObjectException) Then
Return True
Else
Return False
End If
End Function
I feel like this is a poor solution for the problem, though. It seems better than just swallowing all exceptions but not by much.
Is there any exception that encompasses all possible DCOM errors?
Alternatively, is there some other way to detect that a connection to a service has been lost?
EDIT:
Is this situation typical for DCOM in .NET?

Related

Catch Exception Message without stopping

I have a simple Try Catch function like:
Try
'do that
Catch
'didn't work now try again
End Try
Now I want the Error / Catch Message to be displayed as Messagebox which works with:
Catch ex As Exception
Messagebox.Show(ex.message)
The issue with that is that it always Stops / Quits the Application after showing me the Error Message which I want to prevent. This should try something until it works and always display the error message without stopping. Is that possible? I need it as compiled exe so just Debugging won't be an option.
As Explanation what I am trying to do:
Basically I have a Timer that executes every 10 Seconds a Request to my Server. It check if my Server is up and connected. If it can't reach my Server it should exactly display me the Message why it didn't connect but it shouldn't stop, it should continue pinging my server 10 seconds later again and try again until it works. The thing is that the exception message sometimes changes so I really need the Exception Message as output
It sounds like right now you have a method that sometimes throws like this:
Public Sub CheckServer()
' do something to test the server
End Function
If the code is not isolated to it's own method like that, it should be.
And you want to call it like this:
Try
CheckServer()
Catch ex As Exception
MessageBox.Show(...)
End Try
I suggest moving the Try/Catch inside the method and making it return a value:
Public Function CheckServer() As String
Try
' do something to test the server
Catch ex As Exception
Return ex.Message
End Try
Return String.Empty
End Function
And then call it like this:
Dim errorMessage As String = CheckServer()
If Not String.IsNullOrEmpty(errorMessage) Then
SendAlert(errorMessage)
End If
Where you also have a separate alert method:
Public Sub SendAlert(alertText As String)
SomeControl.Text += $"{Now} -- {alertText}{vbCrLf}"
End Sub
Notice I did not use MessageBox, because it blocks your UI. But the idea here is this makes it easy to change what you want to do with the alerts when they come in. For example, you might create a toast notification.
Since you're using WinForms, the other thing you could do here is define and then raise an event, rather than returning a value.
Are you using TCPClient to connect ??? If you are, you can just write an IsConnected code. TCP client stays connected until the server or client go out.
The most common error message when a server is not accepting connections is the “server did not properly respond in time message”
Constantly sending the server a request isn’t ideal. You need a one notification to come through when the connection is lost... and then set the timer to constantly try to reconnect.

Is there a way to handle SQLExceptions that are generated by SqlDataSource

I have been experiencing a problem with one of my vb applications where it is crashing at a certain time of day. In my code, there are only 4 places where that could be the cause of the crash. Three of them are from SQLDataSource queries and the other is in the code behind. I am pretty sure that I don't have a problem with the code behind as I have a using block in place. Further more, inside of that block I have a try catch finally where in the finally I am Disposing the command as well as the connection and Closing the connection. I have been reading some articles that tell me that I should use a SqlDataSource "selected" event to close the connection. I gave that a try but didn't have any success. This is the error that I am receiving:
SqlException (0x80131904): Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
This makes me feel like the "selected" event is not having a chance to get fired. So I thought I should try the "selecting" event. In it, I am trying to grab the connection string and close it. But I am not quite sure I am going down the correct path because I have been unable to catch exceptions inside of that event. Can someone out there please give me a hand with this issue I am facing?
Edit:
This is an example of how I am trying to use the selected event to close the connection
If Not IsNothing(e.Exception) Then
Debug.Print("Exeception encounted while selecting for sqlData")
End If
e.ExceptionHandled = True
And here is and example of how I am trying to use the selecting event (I cannot figure out if an exception has been thrown here).
Dim sqlDataConn As SqlConnection = New SqlConnection("MYConnectionString")
sqlDataConn.Dispose()
If you want to intercept SqlException you need to use explicit "using", i.e. try/catch. Then, you can determine specific issue by the Number property.
try
catch ex as SqlException
MessageBox.Show(ex.Number)
if ex.Number = xxx then
' do something
end if
catch ex as Exception
finally
end try
if the question about handling this
If Not IsNothing(e.Exception) Then
Debug.Print("Exeception encounted while selecting for sqlData")
End If
you can check the exception type
If e.Exception IsNot Nothing AndAlso TypeOf e.Exception Is SqlException Then
.....
It would help if you did a breakpoint and found the query that was having this issue. The timeout issue is when a long-running query kills itself because it ran past the default timeout period.
A quick and dirty solution could be to just use a larger timeout in the connection strings you use, but long running queries can usually be a bad sign of an unoptimized query and should be addressed before your database grows any larger.

Objects are Nothing when referenced by remote client

I have made several VB apps that communicate using Windows Remoting, but this is the first time I ran into this problem.
There are two programs in my project, a client and a server. The server program has a queue. The client program adds items to the queue by calling a server method. But when the server program checks the queue, it is empty.
Furthermore, the server program instantiates several classes, but when the client tries to use them, it finds that they are Nothing. So this is a general problem not just an issue with the queue per se.
I have had experience with threading problems in the past, so I assumed that this was some kind of threading problem. I tried using a delegate function, but that did not help.
Here is a snippet of code to illustrate where the problem appeared. My apologies for not knowing how to make it properly formatted, this is my first attempt.
' ====================================================================
' this class is instantiated on the server at startup time
Public Class CPAutoDispatcher
' EXAMPLE #1
Public mWLQueue As New Collection
' This function is called from the remote client using Windows Remoting
Public Function SendWorkList(ByVal theList As String) As Boolean
Dim objWL As New AutoWorkList
If Not parseWorkList(theList, objWL) Then Exit Function
Call mWLQueue.Add(objWL)
SendWorkList = True
End Function
' This function is called from the server
Public Sub Tick()
If mWLQueue.Count = 0 Then Exit Sub ' <-- THIS ALWAYS EXITS!
Dim objWL As AutoWorkList = mWLQueue.Item(1)
Call mWLQueue.Remove(1)
' ... do something with objWL ...
End Sub
' EXAMPLE #2
Private mServerReports() As CPAutoServerReport
Private mDelGNR As DEL_GetNewReport = AddressOf getNewReportDelegate
' This function is called from the server
Public Function ProcessMessage(objSR As CPAutoServerReport) As Boolean
If mServerReports Is Nothing Then
ReDim mServerReports(0)
mServerReports(0) = objSR
Else
' ... do something else ...
End If
End Function
' This function is called from the remote client using Windows Remoting
Public Function GetNewReport() As CPAutoServerReport
GetNewReport = mDelGNR.Invoke
End Function
Private Function getNewReportDelegate() As CPAutoServerReport
If mServerReports Is Nothing Then Exit Function ' <-- THIS ALWAYS EXITS!
' ... do something with mServerReports ...
End Function
End Class
' ================================================================
Example #1: Similar code in other projects works, so I expected mWLQueue and mServerReports to be reachable by both the server and the client. But they are not. The client finds mWLQueue, but it is empty.
Example #2: If this was simply a threading issue, I would expect the delegate to make thing right. But it does not. The client finds that mServerReports Is Nothing, even after it has been set.
My code is behaving as if there are TWO instances of my CPAutoDispatcher class, one for the server thread, and another for the client thread (the remoting calls). But there is only one global variable, which is referenced by both threads.
I am baffled by this situation. Am I missing something that should be obvious?
The ultimate cause of my problems was the presence of duplicate declarations, which did not cause a compile error, and so went unnoticed. When I removed the extra declarations, all of the weird behavior went away.
I suspect that somehow, an instance of the alternate class was instantiated automatically, and being uninitialized, had an empty queue and references that were Nothing. But I don't understand how that came about.

TPL null reference visual basic task

I'm developing some control software for a robot in Visual Basic and I cannot for the life of me figure out why the execution of a procedure, which works flawlessly outside of the Task framework, does not work when I try to run it concurrently to the rest of the code. I'm trying to use the TPL to let a certain procedure run in the background while allowing the user to use the GUI in parallel (currently, the program freezes during execution of this procedure, and isn't active again until it's over, and this can take hours). The problem here that I couldn't solve by looking at other questions is, my NullReferenceException happens inside the Task area, but if I don't try to run it concurrently to the rest of the software, the exception never, ever happens. This is the code I'm using:
Dim taskSPME = Task(Of Boolean).Factory.StartNew(Function()
Return MainForm.startSPME((SPMEpanel.SPMECoordinates), SPMEpanel.WaitingTimes, SPMEpanel.WellVolumes, SPMEpanel.StirPosition, SPMEpanel.StirringSpeed, SPMEpanel.StirringRadius, SPMEpanel.HoverZ, SPMEpanel.GridScaling, SPMEpanel.IncrementWithin)
End Function)
Try
taskSPME.Wait()
MsgBox("Task return value: " & taskSPME.Result.ToString)
Catch ex As AggregateException
For Each fault In ex.InnerExceptions
If TypeOf (fault) Is InvalidOperationException Then
MsgBox("Invalid Operation Exception")
ElseIf TypeOf (fault) Is NullReferenceException Then
MsgBox("Null Reference Exception. " & fault.Message.ToString)
ElseIf TypeOf (fault) Is IO.FileNotFoundException Then
MsgBox("File not found Exception")
End If
Next
End Try
startSPME(), located in another GUI class called MainForm, is a function that runs fine and returns a boolean value if it's not called from any sort of parallel/concurrent framework. I've tried using Parallel.Invoke() and even the old ThreadPool stuff, and they all just throw the same exception, which happens to be the NullReferenceException where the fault message says "Object reference not set to an instance of an object". I tried performing trivial tasks inside the StartNew() argument, like a MsgBox for example, and those run fine. The imports are
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Thread
Can someone tell me what I'm forgetting here? Are there conditions for the task called by the StartNew() constructor to perform? Like, can it not be located in another class? Originally, I didn't even want to call the Wait() method on my task, since I want the program to go back to the GUI while the robot performs its experiments, but since nothing happens to the robot unless the startSPME() function is outside of that parallel task code, I have no idea what's going wrong here.

thread pool error trapping

Checking on a best practice here.
I am calling a ThreadPool to run an Async process, specifically sending an SSL email with an attachment which seems to take a good long while.
This is just test code, I thought I would drop this in a Try Catch just incase something failed. But will the thread ever even come back here? I have tested this closing the web page, the browser, clicking the back button on the page. The email always makes it through.
Try
System.Threading.ThreadPool.QueueUserWorkItem(AddressOf DoAsyncWork) '
Catch ex As Exception
Throw ex
Exit Sub
End Try
I am not trying to force a failure, yet I guess, but I would like to know how best to trap if the thread fails.
Protected Sub DoAsyncWork(ByVal state As Object)
Dim oMyObject As New sendSSLemail
oMyObject.SSL(userName, , strMessageBody, emailAdd, , permFileLocation, , "CodeMsg")
End Sub
A more convenient way of doing work with the thread pool is to use Task.Factory.StartNew(Action). It returns a Task object which can be Awaited or blocked on with Wait.
Once the task completes, the Exception property of the Task can be used to determine whether an exception was thrown and unhandled by the task's subroutine. (If it's not Nothing, then the InnerException property has the real exception that was thrown.)
Dim task = Task.Factory.StartNew(AddressOf WorkFunction)
' do stuff that doesn't depend on WorkFunction having completed
task.Wait()
If task.Exception IsNot Nothing Then Throw task.Exception.InnerException
After a Throw, the sub is exited anyway (the call stack is unwound looking for a Catch), so the Exit Sub statement does nothing. Wrapping a QueueUserWorkItem call in a try block also does nothing, because any exception would occur on the other thread. You would only get exceptions thrown immediately by QueueUserWorkItem, which off the top of my head only includes complaints about the delegate being Nothing.
Asynchronous tasks can also return values. For more information on that, see the TaskFactory methods that return a Func(Of Task).