
PoC simple pour démontrer les conditions de course sur les Websockets
Dans le domaine des technologies de l'information, garantir la fiabilité et l'intégrité des données est crucial, surtout avec des millions d'utilisateurs et des téraoctets de données en jeu. Cependant, à mesure que les systèmes logiciels deviennent plus complexes, des problèmes tels que les conditions de course peuvent survenir, impactant significativement le fonctionnement du système et conduisant à des résultats imprévisibles.
Les conditions de course sont des erreurs qui se produisent dans les programmes multitâches lorsque deux threads ou processus ou plus tentent de modifier simultanément des données ou des ressources partagées sans synchronisation. Cela peut entraîner des résultats inattendus et imprévisibles, car l'ordre d'exécution des opérations dépend des threads ou processus qui terminent en premier. Détecter les conditions de course est un problème difficile dans les systèmes logiciels en raison de leur nature imprévisible.
Bien qu'il existe de nombreuses ressources disponibles sur Internet concernant les conditions de course classiques, cet article explore si les conditions de course peuvent se produire dans les WebSockets.
Les WebSockets sont une technologie de pointe qui améliore considérablement l'interaction dans les applications web en fournissant une connexion bidirectionnelle ouverte entre le navigateur web d'un utilisateur et un serveur web. Cette connexion transparente permet l'échange de données sans avoir besoin d'initier constamment de nouvelles requêtes HTTP, ce qui les rend idéales pour créer des applications interactives.
Pour démontrer le concept, cet article inclut un code Java qui représente un serveur WebSocket interagissant avec une base de données PostgreSQL. Le serveur utilise la bibliothèque Java-WebSocket pour gérer les connexions WebSocket et effectue les tâches suivantes :
Après le lancement du programme, le code Java se connecte à la base de données et vérifie si la table « example » existe. Si elle n'existe pas, il crée la table et insère des données aléatoires :
Le code le plus intéressant se trouve dans la fonction « onMessage ».
public static int a = 0;
@Override
public void onMessage(WebSocket conn, String message) {
if (a == 0) {
try {
// some activity with db
int rowCount = getCountFromExampleTable();
} catch (SQLException e) {
System.out.println("Error executing query: " + e.getMessage());
}
conn.send("Echo: " + message);
a = a + 1;
System.out.println(a);
}
}
Il y a une variable globale « a » initialisée à 0. Lorsqu'un client se connecte au serveur et envoie un message, il vérifie si « id == 0 », indiquant si cette fonction a déjà été exécutée. Si ce n'est pas le cas, une simple commande SQL est exécutée pour sélectionner le nombre de lignes de la table « example ». Ensuite, « a » est incrémenté de 1, et sa valeur est affichée. Et en théorie, la fonction ne devrait pas être exécutée 2 fois.
En ce qui concerne le client, deux types de clients ont été créés : « WebSocketParallel_Success »
package io.redrays.ws.concept.client;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class WebSocketParallel_Success {
public static void main(String[] args) {
// Define the WebSocket server URI
String serverUri = "ws://127.0.0.1:8080";
// Number of WebSocket clients to create
int numClients = 100;
// Create an ExecutorService to manage multiple WebSocket client threads
ExecutorService executor = Executors.newFixedThreadPool(numClients);
// Create a list to store WebSocket client instances
List<WebSocketClient> clients = new ArrayList<>();
// Loop to create and configure multiple WebSocket clients
for (int i = 0; i < numClients; i++) {
int clientId = i + 1;
try {
// Create a WebSocket client for each connection
WebSocketClient webSocketClient = new WebSocketClient(new URI(serverUri)) {
@Override
public void onOpen(ServerHandshake handshakedata) {
// Handle WebSocket connection opened event
System.out.println("Client " + clientId + " connected to the WebSocket server");
this.send("Hello, WebSocket server! From client " + clientId);
}
@Override
public void onMessage(String message) {
// Handle incoming WebSocket messages
System.out.println("Client " + clientId + " received message: " + message);
}
@Override
public void onClose(int code, String reason, boolean remote) {
// Handle WebSocket connection closed event
System.out.println("Client " + clientId + " connection closed: " + reason);
}
@Override
public void onError(Exception ex) {
// Handle WebSocket error
System.out.println("Client " + clientId + " error occurred: " + ex.getMessage());
}
};
// Add the WebSocket client to the list
clients.add(webSocketClient);
// Connect the WebSocket client in a separate thread
executor.submit(webSocketClient::connect);
} catch (URISyntaxException e) {
System.out.println("Invalid WebSocket server URI: " + e.getMessage());
}
}
// Shutdown the executor after all tasks are submitted
executor.shutdown();
// Wait for all WebSocket client threads to complete
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
System.out.println("Interrupted while waiting for tasks to complete: " + e.getMessage());
}
}
}
et « WebSocketParallel_Failed ».
package io.redrays.ws.concept.client;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;