diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index 5506baeee..fd2e7da08 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -160,8 +160,8 @@ public static class SessionMethods var installation = Db.GetInstallationById(action.InstallationId); installation.TestingMode = action.TestingMode; installation.Apply(Db.Update); - WebsocketManager.InformWebsocketsForInstallation(action.InstallationId); - + await WebsocketManager.InformWebsocketsForInstallation(action.InstallationId); + // Save the configuration change to the database Db.HandleAction(action); return true; @@ -179,7 +179,7 @@ public static class SessionMethods { installation.TestingMode = action.TestingMode; installation.Apply(Db.Update); - WebsocketManager.InformWebsocketsForInstallation(action.InstallationId); + await WebsocketManager.InformWebsocketsForInstallation(action.InstallationId); } Db.UpdateAction(action); @@ -199,7 +199,7 @@ public static class SessionMethods var installation = Db.GetInstallationById(action.InstallationId); installation.TestingMode = false; installation.Apply(Db.Update); - WebsocketManager.InformWebsocketsForInstallation(action.InstallationId); + await WebsocketManager.InformWebsocketsForInstallation(action.InstallationId); } Db.Delete(action); diff --git a/csharp/App/Backend/Websockets/RabbitMQManager.cs b/csharp/App/Backend/Websockets/RabbitMQManager.cs index 3f6afe52d..48d63f116 100644 --- a/csharp/App/Backend/Websockets/RabbitMQManager.cs +++ b/csharp/App/Backend/Websockets/RabbitMQManager.cs @@ -181,7 +181,7 @@ public static class RabbitMqManager //If the status has changed, update all the connected front-ends regarding this installation if(prevStatus != receivedStatusMessage.Status && WebsocketManager.InstallationConnections[installationId].Connections.Count > 0) { - WebsocketManager.InformWebsocketsForInstallation(installationId); + _ = WebsocketManager.InformWebsocketsForInstallation(installationId); // fire-and-forget: sync event handler, can't await } } } diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs index 57ffc7bea..5d521364a 100644 --- a/csharp/App/Backend/Websockets/WebsockerManager.cs +++ b/csharp/App/Backend/Websockets/WebsockerManager.cs @@ -17,6 +17,8 @@ public static class WebsocketManager { while (true) { + var idsToInform = new List(); + lock (InstallationConnections) { Console.WriteLine("Monitoring installation table..."); @@ -31,10 +33,8 @@ public static class WebsocketManager (installationConnection.Value.Product == (int)ProductType.SodiStoreMax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) ) { - Console.WriteLine("Installation ID is " + installationConnection.Key); Console.WriteLine("installationConnection.Value.Timestamp is " + installationConnection.Value.Timestamp); - // Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp)); installationConnection.Value.Status = (int)StatusType.Offline; Installation installation = Db.Installations.FirstOrDefault(f => f.Product == installationConnection.Value.Product && f.Id == installationConnection.Key); @@ -42,42 +42,59 @@ public static class WebsocketManager installation.Apply(Db.Update); if (installationConnection.Value.Connections.Count > 0) { - InformWebsocketsForInstallation(installationConnection.Key); + idsToInform.Add(installationConnection.Key); } } } } + // Send notifications outside the lock so we can await the async SendAsync calls + foreach (var id in idsToInform) + await InformWebsocketsForInstallation(id); + await Task.Delay(TimeSpan.FromMinutes(1)); } } //Inform all the connected websockets regarding installation "installationId" - public static void InformWebsocketsForInstallation(Int64 installationId) + public static async Task InformWebsocketsForInstallation(Int64 installationId) { var installation = Db.GetInstallationById(installationId); - var installationConnection = InstallationConnections[installationId]; - Console.WriteLine("Update all the connected websockets for installation " + installation.Name); - - var jsonObject = new - { - id = installationId, - status = installationConnection.Status, - testingMode = installation.TestingMode - }; + byte[] dataToSend; + List connections; - string jsonString = JsonSerializer.Serialize(jsonObject); - byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString); - - foreach (var connection in installationConnection.Connections) + lock (InstallationConnections) { - connection.SendAsync( - new ArraySegment(dataToSend, 0, dataToSend.Length), - WebSocketMessageType.Text, - true, // Indicates that this is the end of the message - CancellationToken.None - ); + var installationConnection = InstallationConnections[installationId]; + Console.WriteLine("Update all the connected websockets for installation " + installation.Name); + + var jsonObject = new + { + id = installationId, + status = installationConnection.Status, + testingMode = installation.TestingMode + }; + + dataToSend = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(jsonObject)); + connections = installationConnection.Connections.ToList(); // snapshot before releasing lock } + + // Send to all connections concurrently (preserves original fire-and-forget intent), + // but isolate failures so one closed socket doesn't affect others or crash the caller. + await Task.WhenAll(connections + .Where(c => c.State == WebSocketState.Open) + .Select(async c => + { + try + { + await c.SendAsync(new ArraySegment(dataToSend, 0, dataToSend.Length), + WebSocketMessageType.Text, true, CancellationToken.None); + } + catch (Exception ex) + { + Console.WriteLine($"WebSocket send failed for installation {installationId}: {ex.Message}"); + } + })); } @@ -109,9 +126,9 @@ public static class WebsocketManager var jsonString = JsonSerializer.Serialize(jsonObject); var dataToSend = Encoding.UTF8.GetBytes(jsonString); - currentWebSocket.SendAsync(dataToSend, + await currentWebSocket.SendAsync(dataToSend, WebSocketMessageType.Text, - true, + true, CancellationToken.None ); @@ -120,6 +137,7 @@ public static class WebsocketManager //Received a new message from this websocket. //We have a HandleWebSocketConnection per connected frontend + byte[] encodedDataToSend; lock (InstallationConnections) { List dataToSend = new List(); @@ -157,15 +175,7 @@ public static class WebsocketManager } var jsonString = JsonSerializer.Serialize(dataToSend); - var encodedDataToSend = Encoding.UTF8.GetBytes(jsonString); - - - currentWebSocket.SendAsync(encodedDataToSend, - WebSocketMessageType.Text, - true, // Indicates that this is the end of the message - CancellationToken.None - ); - + encodedDataToSend = Encoding.UTF8.GetBytes(jsonString); // Console.WriteLine("Printing installation connection list"); // Console.WriteLine("----------------------------------------------"); @@ -175,6 +185,12 @@ public static class WebsocketManager // } // Console.WriteLine("----------------------------------------------"); } + + await currentWebSocket.SendAsync(encodedDataToSend, + WebSocketMessageType.Text, + true, + CancellationToken.None + ); } lock (InstallationConnections)