I have a VB.NET WinForms application running from an executable stored on a network share. In that application, I have defined the UnhandledException
handler in the ApplicationEvents
(Private Sub MyApplication_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs) Handles Me.UnhandledException
). In my handler, I have a method that logs the exception details to a text file before prompting the user to confirm exiting the application.
However, this application "randomly" crashes and exits completely without any log created or message box displayed. This behavior happens at different executable points in the application and I'm trying desperately to track down the cause. I'm guessing that the problem may be due to either a temporary loss of network connectivity or some other issue communicating with the PostgreSQL database, but I can't confirm the source since there's no stack trace or message detail provided before the application disappears from the user's screen.
This should be "simple", but I'm at a loss as I've tried several things, including wrapping massive blocks of code in Try...Catch
blocks and adding additional logging features to my error handler. I've tried rearranging the code in my UnhandledException
handler to avoid any issues with new object instantiation (for my ErrorHandler
object). I added a check in the error handling for logging the error locally if the network is unavailable. I've even added a simple message box to the FormClosing
event of my main form if the closing wasn't directly initiated by the user to try to at least have the application do something before shutting down completely.
No matter what I've tried so far, the application still forcibly exits during seemingly random times. The user will be pressing a button to execute any of a number of methods that usually work normally. If the user relaunches the application after being kicked out and performs the exact same action again, it works without a problem. What I need to accomplish is some form of "idiot-proofing" the error handling so that whatever is causing the application's exit is caught and logged. I'm sure there are things I'm not thinking of at this point, so let me know if any further clarification is needed.
CODE
The application's Startup
event handler:
Private Sub MyApplication_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup Try Common.ApplicationStartup(ApplicationSettings.CurrentUser) Catch ex As Exception Dim StartupException As New ErrorHandler(ex) StartupException.LogException() MessageBox.Show("You do not have permission to access this resource." & vbCrLf & vbCrLf &"The application will now exit.") System.Environment.Exit(1) End Try' *********************************************************************' ** Notify the user if the application is running in test mode. **' ********************************************************************* If ApplicationSettings.TestMode Then MessageBox.Show("This application is currently running in Test Mode, and will use " &"local paths for data and configuration information." & vbCrLf & vbCrLf &"If you are trying to use this application with live data and see " &"this message, please contact the IT HelpDesk for assistance.", "TEST MODE", MessageBoxButtons.OK, MessageBoxIcon.Exclamation) If ApplicationSettings.CurrentUser.Department = Users.Employee.Department.IS Then If MessageBox.Show("Do you want to continue in Test Mode?", "TEST MODE", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) = DialogResult.No Then ApplicationSettings.TestMode = False End If End If End If' *********************************************************************' ** Initialize any application-specific settings here. **' ********************************************************************* Try'If ApplicationSettings.TestMode AndAlso ApplicationSettings.CurrentUser.Department = Users.Employee.Department.IS Then' MessageBox.Show("If you have any additional parameters/settings to configure for this application, " &'"please do so before commenting out this message.",'"DEVELOPMENT WARNING", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)'End If Catch ex As Exception Dim ExHandling As New Common.ErrorHandler(ex) ExHandling.LogException() MessageBox.Show("There was a problem with initializing the application's configuration." & vbCrLf & vbCrLf &"The application will now exit.") System.Environment.Exit(2) End TryEnd Sub
The ApplicationStartup
method:
Public Sub ApplicationStartup(ByRef CurrentUser As Users.Employee)' *********************************************************************' ** Default the TestMode variable to False. If the check for **' ** whether or not the application is running from the IDE fails, **' ** the application should assume that it's running live. **' ********************************************************************* ApplicationSettings.TestMode = False' *********************************************************************' ** Perform a check of whether or not the application is running **' ** from the IDE or the Debug folder. **' ********************************************************************* SetTestMode()' *********************************************************************' ** Retrieve any parameters sent to the executable from the command **' ** line and determine if the application is running from the task **' ** scheduler. **' ********************************************************************* ApplicationSettings.ScheduledTask = False ApplicationSettings.RuntimeParameters = System.Environment.GetCommandLineArgs().ToList If Not ApplicationSettings.RuntimeParameters Is Nothing AndAlso ApplicationSettings.RuntimeParameters.Count > 0 Then For Each Parameter As String In ApplicationSettings.RuntimeParameters If Parameter.ToUpper.Contains("SCHEDTASK") Then ApplicationSettings.ScheduledTask = True Exit For End If Next End If' *********************************************************************' ** Set up the CurrentUser object by querying Active Directory and **' ** the PostgreSQL database for details. **' ********************************************************************* Try If CurrentUser.ADUserName Is Nothing OrElse String.IsNullOrEmpty(CurrentUser.ADUserName) Then CurrentUser = New Users.Employee(Environment.UserName) End If Catch UserEx As Exception Dim ExHandler As New ErrorHandler(UserEx) ExHandler.LogException() Throw UserEx End Try If CurrentUser Is Nothing Then Throw New Exception("Username " & Environment.UserName & " was not found in Active Directory.") ElseIf CurrentUser.Enabled = False Then Throw New Exception("Username " & Environment.UserName & " is not a currently active employee.") End If' *********************************************************************' ** Default the DBCommandTimeout variable to 30. **' ********************************************************************* ApplicationSettings.DBCommandTimeout = 30End SubPrivate Sub SetTestMode()' *********************************************************************' ** Use the Debug.Assert to call the InTestMode function, which **' ** will set the TestMode variable to True. Debug.Assert will only **' ** execute if the program is running from a debugging version of **' ** the code (in Design-Time, or from the Debug folder). When the **' ** code is running from a compiled executable, the Debug.Assert **' ** statement will be ignored. **' ********************************************************************* Debug.Assert(InTestMode)End SubPrivate Function InTestMode() As Boolean' *********************************************************************' ** Set the global TestMode variable to True. This function is **' ** only called in debug mode using the Debug.Assert method in the **' ** SetTestMode Sub. It will not be called if the application is **' ** running from a compiled executable. **' ********************************************************************* Common.ApplicationSettings.TestMode = True Return TrueEnd Function
The UnhandledException
event handler:
Private Sub MyApplication_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs) Handles Me.UnhandledException Dim Response As DialogResult = DialogResult.Yes Response = MessageBox.Show("An unknown error occurred in the application." & vbCrLf & vbCrLf &"Do you want to exit the application?", "UNHANDLED EXCEPTION", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1) Dim UnhandledError As New ErrorHandler(e.Exception) UnhandledError.LogException() If Response = DialogResult.Yes Then e.ExitApplication = True Else e.ExitApplication = False End IfEnd Sub
The main form's FormClosing
event:
Private Sub frmMain_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing If Not e.CloseReason = CloseReason.UserClosing Then MessageBox.Show("The application has encountered some sort of problem and is closing.") End IfEnd Sub
Let me know if you want/need to see more code. As I said, the error occurs at seemingly random points in the application's execution and are inconsistent between one try or another.
UPDATE 7/1/2020
I haven't come back to this topic for a while because I moved a copy of the executable (and the supporting libraries) to the user's local drive and had her run the application from there. While she was using that copy, she wasn't "booted" from the program as described above (she had a few errors here and there, but those were all handled by my exception handling routine as expected).
Here we are a few months later and I've had cause to switch the user back to using the copy of the executable from the network share. I just received a report from the user that she's once again experiencing the issue with being randomly kicked out of the application without any warning or error and I'm not getting any of my exception "reports". Luckily for me, she's done a decent job of documenting the occurrences.
The strange thing is that sometimes these crashes occur when she's not doing anything "special". A couple of times they've happened when she simply clicks on one of the toolstrip menus to display a drop-down list of submenus. I've checked and there isn't any event handling code for these parent toolstrip menus, so it isn't like there are any queries or other instructions being executed. It should simply be displaying the submenu.
FWIW, a couple of weeks ago, we had a serious connectivity issue between our office and the server on which these executables are being stored (hosted VM's accessed via site-to-site VPN). I was getting around 10% packet loss across the VPN, even though I didn't see any packet loss anywhere else. I never found out what was causing the packet loss, but it appears to have been resolved and I can only assume that one of the ISP's between here and there had a faulty piece of equipment that they repaired/replaced. When I run a PING test to the server across the VPN, I'm not seeing any significant packet loss (maybe 1 packet out of several thousand) and response times of 15-35ms.
At this point, I'm only guessing (obviously), but I'm thinking that perhaps there's some sort of "time-out" occurring on the VPN connection that's causing a loss of connection to the code base. It's a total pS.W.A.G. ((pseudo) Scientific Wild-@$$ Guess), but I'm trying to come up with a viable solution to address the issue.
MY IDEAS
One thought is this: All of my in-house applications are run from this server and all of the supporting libraries for each are stored in the executable folder. Yes, this means that I've got multiple copies of many libraries stored on the server in various folders. I've been wanting to reduce this duplication, but I haven't really had/taken the time to figure out the best way to do so. At this point, I'm considering some sort of "installer" package for each of the workstations to drop the necessary libraries into each user's GAC (Global Assembly Cache) instead of accessing them through the VPN.
The only problem with this (that I can think of) is that there are several legacy systems that use different versions of the same libraries. For example, my current development is using Npgsql v4.1.3.1, but there are some applications that are still using v2.x and I don't really have time to go through every application to find which ones are/aren't using the current version and implement a version upgrade. That's just one of the many libraries where such an issue would arise, so I suppose I'd need to try to install all of the in-use versions to each GAC.
Another thought: Bring all of the executables back to a local server (not over the VPN) and change all of the shortcuts to point to that version instead of the one that requires the VPN. This, obviously, would have the benefit of less dependency on things like Internet connectivity and 3rd-party systems, as well as reduced latency.
The issue with this option, however, is that it's completely "unsupported" by my bosses. Their response when I've suggested something similar in the past is along the lines of "we're paying for a hosted server and they should support it..." Well, we all know that something like this may well be beyond the scope of any reasonable support request for a 3rd-party server host.
I'm really leaning towards the GAC option - at least as a first step - but I'll need to do a bit of research before I start traipsing down that road. Does anyone else have other suggestions for ways I might be able to deal with this? I'm really running out of ideas and I've got to find a real, workable and sustainable solution.
MORE INFO
I've implemented the suggestion below from @djv of wrapping the application's launch in a "startup" form that starts a new thread, but that still hasn't been able to catch whatever is causing the crash. The application still just periodically dies with absolutely no logging that I've been able to find so far.
I've also included, in the ApplicationEvents
, a very simple handler for the NetworkAvailabilityChanged
event to try to catch something happening there.
Private Sub MyApplication_NetworkAvailabilityChanged(sender As Object, e As NetworkAvailableEventArgs) Handles Me.NetworkAvailabilityChanged If Not My.Computer.Network.IsAvailable Then MessageBox.Show("Network connection has been lost.", "NETWORK CONNECTION TESTING", MessageBoxButtons.OK, MessageBoxIcon.Exclamation) End If End Sub
Unfortunately, even that hasn't given me any additional insights as the user hasn't ever seen that MessageBox
.
I have found an error in the user's Windows Event Logs that seems to correspond to the most recent event, but I'm not sure what it means, exactly:
Faulting application name: <EXECUTABLE_NAME>.exe, version: 1.0.0.0, time stamp: 0x9d491d36Faulting module name: clr.dll, version: 4.8.4180.0, time stamp: 0x5e7d1ed7Exception code: 0xc0000006Fault offset: 0x000cc756Faulting process id: 0xe570Faulting application start time: 0x01d64fc245d7d922Faulting application path: \\<SERVER_NAME>\<SHARE_PATH>\<EXECUTABLE_NAME>.exeFaulting module path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dllReport Id: 7016e3cc-7406-4854-95be-dbe3231447e7Faulting package full name: Faulting package-relative application ID:
This seems to indicate something in the CLR crapping out, but that really doesn't seem to give me any more information than I had before.