
PoC simple para demostrar condiciones de carrera en Websockets
En el campo de la tecnología de la información, garantizar la fiabilidad e integridad de los datos es crucial, especialmente con millones de usuarios y terabytes de datos en juego. Sin embargo, a medida que los sistemas de software se vuelven más complejos, pueden surgir problemas como las condiciones de carrera, que afectan significativamente el funcionamiento del sistema y provocan resultados impredecibles.
Las condiciones de carrera son errores que ocurren en programas multitarea cuando dos o más hilos o procesos intentan modificar datos o recursos compartidos simultáneamente sin sincronización. Esto puede dar lugar a resultados inesperados e impredecibles, ya que el orden de ejecución de las operaciones depende de qué hilos o procesos terminen primero. Detectar condiciones de carrera es un problema complejo en los sistemas de software debido a su naturaleza impredecible.
Aunque hay numerosos recursos disponibles en internet sobre las condiciones de carrera clásicas, este artículo explora si las condiciones de carrera pueden ocurrir en WebSockets.
Los WebSockets son una tecnología de vanguardia que mejora significativamente la interacción en las aplicaciones web al proporcionar una conexión bidireccional abierta entre el navegador web del usuario y un servidor web. Esta conexión fluida permite el intercambio de datos sin necesidad de iniciar constantemente nuevas solicitudes HTTP, lo que los hace ideales para crear aplicaciones interactivas.
Para demostrar el concepto, este artículo incluye un código Java que representa un servidor WebSocket que interactúa con una base de datos PostgreSQL. El servidor utiliza la biblioteca Java-WebSocket para gestionar las conexiones WebSocket y realiza las siguientes tareas:
Después de iniciar el programa, el código Java se conecta a la base de datos y comprueba si existe la tabla "example". Si no existe, crea la tabla e inserta datos aleatorios:
El código más interesante se encuentra en la función "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);
}
}
Hay una variable global "a" inicializada a 0. Cuando un cliente se conecta al servidor y envía un mensaje, se comprueba si "id == 0", lo que indica si esta función ya se ha ejecutado. Si no es así, se ejecuta un comando SQL simple para seleccionar el recuento de filas de la tabla "example". Luego, "a" se incrementa en 1 y se imprime su valor. Y en teoría, la función no debería ejecutarse 2 veces.
En cuanto al cliente, se han creado dos tipos de clientes: "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());
}
}
}
y "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;
public class WebSocketParallel_Failed {