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:
Developer01
2026-09-29 15:49:15 +02:00
parent 298a398067
commit de918b1e88
6 changed files with 439 additions and 51 deletions

View File

@@ -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