Verbesserung der Stabilität und Fehlerbehandlung
- `MSSQLServer.vb`: Konvertierung von `Nothing`-Werten zu `DBNull.Value` und detaillierte Protokollierung von SQL-Fehlern hinzugefügt. - `AssemblyInfo.vb`: Aktualisierung der Assembly-Version auf `2.4.0.0`. - `Client.vb`: Einführung von `_IsReconnecting`, um parallele Reconnects zu verhindern. Verbesserte Fehlerbehandlung, Protokollierung und Ressourcenfreigabe. - `MailSession.vb`: Konsistentere Protokollierung, Änderung von `Info` zu `Debug` beim Schließen der Verbindung. - `EDMI.API.vbproj`: Hinzufügen der Datei `ErrorLog.txt`. - `ErrorLog.txt`: Dokumentation von Timeout- und Verbindungsproblemen mit detaillierten Stack-Traces. Diese Änderungen verbessern die Stabilität, Fehlertoleranz und Protokollierungsqualität der Anwendung.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
Imports System.IO
|
||||
Imports System.ServiceModel
|
||||
Imports System.Threading
|
||||
Imports DigitalData.Modules.Base
|
||||
Imports DigitalData.Modules.EDMI.API.Constants
|
||||
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
|
||||
@@ -23,6 +24,8 @@ Public Class Client
|
||||
Private _ClientConfig As GlobalStateClientConfiguration
|
||||
Private _CachedTables As New List(Of String)
|
||||
Private _IsOnline As Boolean
|
||||
' Verhindert parallele Reconnect-Versuche (0 = frei, 1 = läuft)
|
||||
Private _IsReconnecting As Integer = 0
|
||||
Private _Channel As IEDMIServiceChannel
|
||||
Private _ClientName As String
|
||||
' Static Registry
|
||||
@@ -52,23 +55,26 @@ Public Class Client
|
||||
End Sub
|
||||
Protected Overridable Sub Dispose(disposing As Boolean)
|
||||
If _Disposed Then Return
|
||||
|
||||
If disposing Then
|
||||
_IsOnline = False ' ← Reconnect-Guard deaktivieren
|
||||
UpdateTimer.Stop()
|
||||
UpdateTimer.Dispose()
|
||||
|
||||
' Auf laufenden Reconnect warten
|
||||
Dim oTimeout = DateTime.Now.AddSeconds(5)
|
||||
While _IsReconnecting = 1 AndAlso DateTime.Now < oTimeout
|
||||
Task.Delay(100).Wait()
|
||||
End While
|
||||
|
||||
If _Channel IsNot Nothing Then
|
||||
Channel.CloseChannel(_Channel)
|
||||
_Channel = Nothing
|
||||
End If
|
||||
|
||||
' Aus Registry austragen
|
||||
Dim oRemoved As ClientStatus = Nothing
|
||||
_ActiveClients.TryRemove(_ClientId, oRemoved)
|
||||
Logger.Debug("Client [{0}] unregistered. Total active clients: [{1}]", _ClientName, _ActiveClients.Count)
|
||||
Logger.Debug("Client disposed. Channel closed.")
|
||||
End If
|
||||
|
||||
_Disposed = True
|
||||
End Sub
|
||||
|
||||
@@ -125,7 +131,7 @@ Public Class Client
|
||||
_ClientId = $"{_ClientName}_{Guid.NewGuid().ToString("N").Substring(0, 8)}" ' ← eindeutig
|
||||
|
||||
UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
|
||||
UpdateTimer.Start()
|
||||
' Start erfolgt in Connect()
|
||||
|
||||
Try
|
||||
Dim oConnection = New Connection()
|
||||
@@ -154,7 +160,6 @@ Public Class Client
|
||||
_ClientId = $"{_ClientName}_{Guid.NewGuid().ToString("N").Substring(0, 8)}" ' ← eindeutig
|
||||
|
||||
UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
|
||||
UpdateTimer.Start()
|
||||
|
||||
Try
|
||||
Dim oConnection = New Connection()
|
||||
@@ -188,7 +193,6 @@ Public Class Client
|
||||
|
||||
Logger.Info("✔✔Connection to AppService [{0}] successfully established! | Client=[{1}] | Id=[{2}]",
|
||||
ServerAddress, _ClientName, _ClientId)
|
||||
_IsOnline = True
|
||||
|
||||
' CachedTables sofort befüllen – nicht auf den ersten Timer-Tick warten
|
||||
Try
|
||||
@@ -199,14 +203,17 @@ Public Class Client
|
||||
Logger.Warn("⚠ CachedTables could not be loaded on connect: {0}", ex.Message)
|
||||
_CachedTables = New List(Of String)
|
||||
End Try
|
||||
' In Registry eintragen
|
||||
|
||||
_IsOnline = True ' ← vor GetStatus()
|
||||
UpdateTimer.Start()
|
||||
|
||||
' In Registry eintragen – jetzt mit korrektem IsOnline=True
|
||||
_ActiveClients(_ClientId) = GetStatus()
|
||||
Logger.Info("Client [{0}] registered with Id=[{1}]. Total active clients: [{2}]", _ClientName, _ClientId, _ActiveClients.Count)
|
||||
|
||||
Return True
|
||||
Catch ex As Exception
|
||||
_IsOnline = False
|
||||
|
||||
Logger.Error(ex)
|
||||
Return False
|
||||
End Try
|
||||
@@ -215,30 +222,46 @@ Public Class Client
|
||||
''' <summary>
|
||||
''' Aborts the channel and creates a new connection
|
||||
''' </summary>
|
||||
Public Async Sub Reconnect()
|
||||
Logger.Warn("⚠ Connection faulted. Trying to reconnect..")
|
||||
Public Async Sub Reconnect(Optional sender As Object = Nothing, Optional e As EventArgs = Nothing)
|
||||
' Nur ein Reconnect gleichzeitig erlauben – atomar prüfen und setzen
|
||||
If Interlocked.CompareExchange(_IsReconnecting, 1, 0) = 1 Then
|
||||
Logger.Debug("Reconnect already in progress – skipping duplicate call.")
|
||||
Return
|
||||
End If
|
||||
|
||||
Dim oRetryDelays = {2, 5, 15, 30}
|
||||
Try
|
||||
Logger.Warn("⚠ Connection faulted. Trying to reconnect..")
|
||||
|
||||
For Each oDelay In oRetryDelays
|
||||
Try
|
||||
Await Task.Delay(TimeSpan.FromSeconds(oDelay))
|
||||
Logger.Debug($"Reconnect attempt after {oDelay}s delay...")
|
||||
Dim oRetryDelays = {2, 5, 15, 30}
|
||||
|
||||
Channel.CloseChannel(_Channel) ' Sauber schließen statt Abort
|
||||
_Channel = ChannelManager.GetChannel()
|
||||
_Channel.Open()
|
||||
For Each oDelay In oRetryDelays
|
||||
Try
|
||||
Await Task.Delay(TimeSpan.FromSeconds(oDelay))
|
||||
Logger.Debug("Reconnect attempt after [{0}]s delay...", oDelay)
|
||||
|
||||
_IsOnline = True
|
||||
Logger.Info("Reconnect successful.")
|
||||
Return
|
||||
Catch ex As Exception
|
||||
Logger.Warn($"⚠ Reconnect failed: {ex.Message}")
|
||||
_IsOnline = False
|
||||
End Try
|
||||
Next
|
||||
Channel.CloseChannel(_Channel)
|
||||
_Channel = ChannelManager.GetChannel()
|
||||
_Channel.Open()
|
||||
|
||||
Logger.Error("All reconnect attempts failed. Service unavailable.")
|
||||
_IsOnline = True
|
||||
|
||||
If _ActiveClients.ContainsKey(_ClientId) Then
|
||||
_ActiveClients(_ClientId) = GetStatus()
|
||||
End If
|
||||
|
||||
Logger.Info("✔ Reconnect successful.")
|
||||
Return
|
||||
Catch ex As Exception
|
||||
Logger.Warn("⚠ Reconnect failed after [{0}]s: {1}", oDelay, ex.Message)
|
||||
_IsOnline = False
|
||||
End Try
|
||||
Next
|
||||
|
||||
Logger.Error("All reconnect attempts failed. Service unavailable.")
|
||||
Finally
|
||||
' Flag immer zurücksetzen – auch bei unerwarteten Fehlern
|
||||
Interlocked.Exchange(_IsReconnecting, 0)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
Private Async Sub UpdateTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) Handles UpdateTimer.Elapsed
|
||||
@@ -246,7 +269,7 @@ Public Class Client
|
||||
If _Channel Is Nothing OrElse _Channel.State <> CommunicationState.Opened Then
|
||||
Logger.Warn("⚠ UpdateTimer skipped: Channel not in Opened state [{0}]", If(_Channel IsNot Nothing, _Channel.State.ToString(), "null"))
|
||||
_IsOnline = False
|
||||
Reconnect()
|
||||
Reconnect(sender, e)
|
||||
Return
|
||||
End If
|
||||
|
||||
@@ -825,10 +848,8 @@ Public Class Client
|
||||
})
|
||||
Catch ex As EndpointNotFoundException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
|
||||
Throw
|
||||
Catch ex As CommunicationException
|
||||
HandleCommunicationError(ex, NameOf(ExecuteNonQueryFromIDB))
|
||||
Throw
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex)
|
||||
Throw
|
||||
@@ -974,15 +995,15 @@ Public Class Client
|
||||
|
||||
Catch ex As FaultException(Of ObjectDoesNotExistFault)
|
||||
Logger.Error(ex)
|
||||
Throw ex
|
||||
Throw
|
||||
|
||||
Catch ex As FaultException
|
||||
Logger.Error(ex)
|
||||
Throw ex
|
||||
Throw
|
||||
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex)
|
||||
Throw ex
|
||||
Throw
|
||||
|
||||
End Try
|
||||
End Function
|
||||
@@ -1001,7 +1022,7 @@ Public Class Client
|
||||
}
|
||||
Catch ex As Exception
|
||||
Logger.Error(ex)
|
||||
Throw ex
|
||||
Throw
|
||||
End Try
|
||||
End Function
|
||||
''' <summary>
|
||||
@@ -1035,12 +1056,27 @@ Public Class Client
|
||||
Throw New InvalidOperationException("Channel is not initialized. Call Connect() first.")
|
||||
End If
|
||||
|
||||
If _Channel.State <> CommunicationState.Opened Then
|
||||
Throw New InvalidOperationException($"Channel is not available. State=[{_Channel.State}]")
|
||||
If Not _IsOnline Then
|
||||
' Kurze Grace-Period abwarten, damit Reconnect() seinen _IsReconnecting-Flag setzen kann
|
||||
Dim oGraceLimit = DateTime.Now.AddMilliseconds(600)
|
||||
While DateTime.Now < oGraceLimit AndAlso _IsReconnecting = 0 AndAlso Not _IsOnline
|
||||
Task.Delay(100).Wait()
|
||||
End While
|
||||
|
||||
' Jetzt auf aktiven Reconnect warten
|
||||
Dim oWaitLimit = DateTime.Now.AddSeconds(10)
|
||||
While Not _IsOnline AndAlso DateTime.Now < oWaitLimit AndAlso _IsReconnecting = 1
|
||||
Task.Delay(500).Wait()
|
||||
Logger.Debug("EnsureAvailable: Waiting for reconnect... Channel=[{0}]", _Channel.State.ToString())
|
||||
End While
|
||||
|
||||
If Not _IsOnline Then
|
||||
Throw New InvalidOperationException("Client is offline.")
|
||||
End If
|
||||
End If
|
||||
|
||||
If Not _IsOnline Then
|
||||
Throw New InvalidOperationException("Client is offline.")
|
||||
If _Channel.State <> CommunicationState.Opened Then
|
||||
Throw New InvalidOperationException($"Channel is not available. State=[{_Channel.State}]")
|
||||
End If
|
||||
End Sub
|
||||
''' <summary>
|
||||
@@ -1049,11 +1085,11 @@ Public Class Client
|
||||
Private Sub HandleCommunicationError(ex As Exception, callerName As String)
|
||||
Select Case True
|
||||
Case TypeOf ex Is EndpointNotFoundException
|
||||
Logger.Warn("⚠ Service not reachable (EndpointNotFoundException in {0}). Triggering reconnect...", callerName)
|
||||
Logger.Warn("⚠ Service not reachable (EndpointNotFoundException in [{0}]). Triggering reconnect...", callerName)
|
||||
Case TypeOf ex Is CommunicationException
|
||||
Logger.Warn("⚠ in {0} during call. Triggering reconnect...", callerName)
|
||||
Logger.Warn("⚠ CommunicationException in [{0}] during call. Triggering reconnect...", callerName)
|
||||
Case Else
|
||||
Logger.Warn("⚠ Unexpected exception in {0} during call. Triggering reconnect...", callerName)
|
||||
Logger.Warn("⚠ Unexpected exception in [{0}] during call. Triggering reconnect...", callerName)
|
||||
Logger.Error(ex)
|
||||
End Select
|
||||
|
||||
|
||||
Reference in New Issue
Block a user