bullshit fixes

This commit is contained in:
2025-11-06 20:31:59 +00:00
parent 5dc1b40c88
commit 84cba39597
3 changed files with 390 additions and 369 deletions

View File

@@ -22,7 +22,8 @@ import sd.protocol.SocketConnection;
/** /**
* Main class for an Intersection Process in the distributed traffic simulation. * Main class for an Intersection Process in the distributed traffic simulation.
* * Each IntersectionProcess runs as an independent Java application (JVM instance) * * Each IntersectionProcess runs as an independent Java application (JVM
* instance)
* representing one of the five intersections (Cr1-Cr5) in the network. * representing one of the five intersections (Cr1-Cr5) in the network.
*/ */
public class IntersectionProcess { public class IntersectionProcess {
@@ -41,13 +42,14 @@ public class IntersectionProcess {
private final ExecutorService trafficLightPool; private final ExecutorService trafficLightPool;
private volatile boolean running; //Quando uma thread escreve um valor volatile, todas as outras private volatile boolean running; // Quando uma thread escreve um valor volatile, todas as outras
//threads veem a mudança imediatamente. // threads veem a mudança imediatamente.
// Traffic Light Coordination // Traffic Light Coordination
/** /**
* Lock to ensure mutual exclusion between traffic lights. * Lock to ensure mutual exclusion between traffic lights.
* Only one traffic light can be green at any given time within this intersection. * Only one traffic light can be green at any given time within this
* intersection.
*/ */
private final Lock trafficCoordinationLock; private final Lock trafficCoordinationLock;
@@ -91,7 +93,8 @@ public class IntersectionProcess {
} }
/** /**
* Creates traffic lights for this intersection based on its physical connections. * Creates traffic lights for this intersection based on its physical
* connections.
* Each intersection has different number and directions of traffic lights * Each intersection has different number and directions of traffic lights
* according to the network topology. * according to the network topology.
*/ */
@@ -101,19 +104,19 @@ public class IntersectionProcess {
String[] directions = new String[0]; String[] directions = new String[0];
switch (intersectionId) { switch (intersectionId) {
case "Cr1": case "Cr1":
directions = new String[]{"East", "South"}; directions = new String[] { "East", "South" };
break; break;
case "Cr2": case "Cr2":
directions = new String[]{"West", "East", "South"}; directions = new String[] { "West", "East", "South" };
break; break;
case "Cr3": case "Cr3":
directions = new String[]{"West", "South"}; directions = new String[] { "West", "South" };
break; break;
case "Cr4": case "Cr4":
directions = new String[]{"East"}; directions = new String[] { "East" };
break; break;
case "Cr5": case "Cr5":
directions = new String[]{"East"}; directions = new String[] { "East" };
break; break;
} }
@@ -125,8 +128,7 @@ public class IntersectionProcess {
intersectionId + "-" + direction, intersectionId + "-" + direction,
direction, direction,
greenTime, greenTime,
redTime redTime);
);
intersection.addTrafficLight(light); intersection.addTrafficLight(light);
System.out.println(" Created traffic light: " + direction + System.out.println(" Created traffic light: " + direction +
@@ -314,8 +316,7 @@ public class IntersectionProcess {
MessageProtocol message = new VehicleTransferMessage( MessageProtocol message = new VehicleTransferMessage(
intersectionId, intersectionId,
nextDestination, nextDestination,
vehicle vehicle);
);
connection.sendMessage(message); connection.sendMessage(message);
@@ -406,17 +407,29 @@ public class IntersectionProcess {
try { try {
Socket clientSocket = serverSocket.accept(); Socket clientSocket = serverSocket.accept();
System.out.println("[" + intersectionId + "] New connection accepted from " +
clientSocket.getInetAddress().getHostAddress());
// Check running flag again before handling - prevents accepting during shutdown
if (!running) {
clientSocket.close();
break;
}
// Handle each connection in a separate thread // Handle each connection in a separate thread
connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket)); connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));
} catch (IOException e) { } catch (IOException e) {
if (running) { // Expected when serverSocket.close() is called during shutdown
if (!running) {
break; // Normal shutdown
}
// Unexpected error during normal operation
System.err.println("[" + intersectionId + "] Error accepting connection: " + System.err.println("[" + intersectionId + "] Error accepting connection: " +
e.getMessage()); e.getMessage());
} }
} }
} }
}
/** /**
* Handles an incoming connection from another process. * Handles an incoming connection from another process.
@@ -427,6 +440,9 @@ public class IntersectionProcess {
private void handleIncomingConnection(Socket clientSocket) { private void handleIncomingConnection(Socket clientSocket) {
try (SocketConnection connection = new SocketConnection(clientSocket)) { try (SocketConnection connection = new SocketConnection(clientSocket)) {
// Set socket timeout so receiveMessage() won't block forever
clientSocket.setSoTimeout(1000); // 1 second timeout
System.out.println("[" + intersectionId + "] New connection accepted from " + System.out.println("[" + intersectionId + "] New connection accepted from " +
clientSocket.getInetAddress().getHostAddress()); clientSocket.getInetAddress().getHostAddress());
@@ -445,9 +461,16 @@ public class IntersectionProcess {
intersection.receiveVehicle(vehicle); intersection.receiveVehicle(vehicle);
} }
} catch (java.net.SocketTimeoutException e) {
// Timeout is expected - just check running flag and continue
if (!running) {
break;
}
// Continue waiting for next message
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
System.err.println("[" + intersectionId + "] Unknown message type received: " + System.err.println("[" + intersectionId + "] Unknown message type received: " +
e.getMessage()); e.getMessage());
break; // Invalid message, close connection
} }
} }
@@ -455,6 +478,7 @@ public class IntersectionProcess {
if (running) { if (running) {
System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage()); System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
} }
// Expected during shutdown
} }
} }
@@ -466,44 +490,43 @@ public class IntersectionProcess {
System.out.println("\n[" + intersectionId + "] Shutting down..."); System.out.println("\n[" + intersectionId + "] Shutting down...");
running = false; running = false;
// Close server socket
try {
if (serverSocket != null && !serverSocket.isClosed()) { if (serverSocket != null && !serverSocket.isClosed()) {
try {
serverSocket.close(); serverSocket.close();
}
} catch (IOException e) { } catch (IOException e) {
System.err.println("[" + intersectionId + "] Error closing server socket: " + System.err.println("[" + intersectionId + "] Error closing server socket: " +
e.getMessage()); e.getMessage());
} }
}
synchronized (outgoingConnections) {
for (SocketConnection conn : outgoingConnections.values()) {
try {
conn.close();
} catch (Exception e) {
// Ignore errors during shutdown
}
}
outgoingConnections.clear();
}
// Shutdown thread pools
trafficLightPool.shutdown(); trafficLightPool.shutdown();
connectionHandlerPool.shutdown(); connectionHandlerPool.shutdownNow(); // Use shutdownNow() to interrupt running tasks
try { try {
if (!trafficLightPool.awaitTermination(5, TimeUnit.SECONDS)) { if (!trafficLightPool.awaitTermination(2, TimeUnit.SECONDS)) {
trafficLightPool.shutdownNow(); trafficLightPool.shutdownNow();
} }
if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) { if (!connectionHandlerPool.awaitTermination(2, TimeUnit.SECONDS)) {
connectionHandlerPool.shutdownNow(); connectionHandlerPool.shutdownNow();
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
trafficLightPool.shutdownNow(); trafficLightPool.shutdownNow();
connectionHandlerPool.shutdownNow(); connectionHandlerPool.shutdownNow();
} Thread.currentThread().interrupt();
// Close all outgoing connections
for (Map.Entry<String, SocketConnection> entry : outgoingConnections.entrySet()) {
try {
entry.getValue().close();
} catch (IOException e) {
System.err.println("[" + intersectionId + "] Error closing connection to " +
entry.getKey() + ": " + e.getMessage());
}
} }
System.out.println("[" + intersectionId + "] Shutdown complete."); System.out.println("[" + intersectionId + "] Shutdown complete.");
System.out.println("=".repeat(60)); System.out.println("=".repeat(60) + "\n");
} }
/** /**

View File

@@ -9,150 +9,100 @@ import sd.model.Vehicle;
/** /**
* Implements the control logic for a single TrafficLight * Implements the control logic for a single TrafficLight
* as a Runnable task that runs in its own Thread. * as a Runnable task that runs in its own Thread.
*
*/ */
public class TrafficLightThread implements Runnable { public class TrafficLightThread implements Runnable {
/**
* The TrafficLight object (the *model*) that this thread controls.
* Contains the queue and the state.
*/
private final TrafficLight light; private final TrafficLight light;
/**
* The IntersectionProcess (the Process) that "owns" this thread.
* Used to call methods on the process, such as sendVehicleToNextDestination().
*/
private final IntersectionProcess process; private final IntersectionProcess process;
/**
* The simulation configuration, used to get timings (e.g., crossing time).
*/
private final SimulationConfig config; private final SimulationConfig config;
/**
* Volatile flag to control the graceful shutdown mechanism.
* When set to 'false', the 'run()' loop terminates.
*/
private volatile boolean running; private volatile boolean running;
/** // Store the thread reference for proper interruption
* Constructor for the Traffic Light Thread. private Thread currentThread;
*
* @param light The TrafficLight object (model) to be controlled.
* @param process The parent IntersectionProcess (for callbacks).
* @param config The simulation configuration (to get timings).
*/
public TrafficLightThread(TrafficLight light, IntersectionProcess process, SimulationConfig config) { public TrafficLightThread(TrafficLight light, IntersectionProcess process, SimulationConfig config) {
this.light = light; this.light = light;
this.process = process; this.process = process;
this.config = config; this.config = config;
this.running = false; // Starts as 'stopped' this.running = false;
} }
/**
* The main entry point for the thread.
* Implements the GREEN/RED cycle logic extracted from IntersectionProcess.
*
*/
@Override @Override
public void run() { public void run() {
// Capture the current thread reference
this.currentThread = Thread.currentThread();
this.running = true; this.running = true;
System.out.println("[" + light.getId() + "] Traffic light thread started."); System.out.println("[" + light.getId() + "] Traffic light thread started.");
try { try {
// Main thread loop, continues while 'running' is true while (running && !Thread.currentThread().isInterrupted()) {
// This 'running' flag is controlled by the parent IntersectionProcess
while (running) {
// --- GREEN Phase --- // --- GREEN Phase ---
light.changeState(TrafficLightState.GREEN); // light.changeState(TrafficLightState.GREEN);
System.out.println("[" + light.getId() + "] State: GREEN"); System.out.println("[" + light.getId() + "] State: GREEN");
// Process vehicles in the queue
processGreenLightQueue(); processGreenLightQueue();
// Wait for green duration if (!running || Thread.currentThread().isInterrupted()) break;
Thread.sleep((long) (light.getGreenTime() * 1000)); //
if (!running) break; // Check flag after sleep // Wait for green duration
Thread.sleep((long) (light.getGreenTime() * 1000));
if (!running || Thread.currentThread().isInterrupted()) break;
// --- RED Phase --- // --- RED Phase ---
light.changeState(TrafficLightState.RED); // light.changeState(TrafficLightState.RED);
System.out.println("[" + light.getId() + "] State: RED"); System.out.println("[" + light.getId() + "] State: RED");
// Wait for red duration // Wait for red duration
Thread.sleep((long) (light.getRedTime() * 1000)); // Thread.sleep((long) (light.getRedTime() * 1000));
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
// Apanha a InterruptedException (outra forma de parar a thread)
System.out.println("[" + light.getId() + "] Traffic light thread interrupted."); System.out.println("[" + light.getId() + "] Traffic light thread interrupted.");
this.running = false; // Garante que o loop termina // Restore interrupt status
} Thread.currentThread().interrupt();
} finally {
this.running = false;
System.out.println("[" + light.getId() + "] Traffic light thread stopped."); System.out.println("[" + light.getId() + "] Traffic light thread stopped.");
} }
}
/**
* Processes vehicles in the queue while the traffic light is GREEN.
* Logic extracted from IntersectionProcess.processGreenLight()
*
*/
private void processGreenLightQueue() throws InterruptedException { private void processGreenLightQueue() throws InterruptedException {
// while (running && !Thread.currentThread().isInterrupted()
while (running && light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) { && light.getState() == TrafficLightState.GREEN
&& light.getQueueSize() > 0) {
Vehicle vehicle = light.removeVehicle(); // Vehicle vehicle = light.removeVehicle();
if (vehicle != null) { if (vehicle != null) {
// 1. Get the crossing time (t_sem) double crossingTime = getCrossingTimeForVehicle(vehicle);
double crossingTime = getCrossingTimeForVehicle(vehicle); //
// 2. Simulate the time the vehicle takes to cross Thread.sleep((long) (crossingTime * 1000));
Thread.sleep((long) (crossingTime * 1000)); //
// 3. Update vehicle statistics vehicle.addCrossingTime(crossingTime);
vehicle.addCrossingTime(crossingTime); // process.getIntersection().incrementVehiclesSent();
process.sendVehicleToNextDestination(vehicle);
// 4. Update intersection statistics
process.getIntersection().incrementVehiclesSent(); //
// 5. Call the parent Process to send the vehicle
process.sendVehicleToNextDestination(vehicle); //
} }
} }
} }
/**
* Gets the crossing time for a vehicle based on its type.
* Logic extracted from IntersectionProcess.getCrossingTimeForVehicle()
*
*
* @param vehicle The vehicle.
* @return The crossing time in seconds.
*/
private double getCrossingTimeForVehicle(Vehicle vehicle) { private double getCrossingTimeForVehicle(Vehicle vehicle) {
switch (vehicle.getType()) { // return switch (vehicle.getType()) {
case BIKE: case BIKE -> config.getBikeVehicleCrossingTime();
return config.getBikeVehicleCrossingTime(); // case LIGHT -> config.getLightVehicleCrossingTime();
case LIGHT: case HEAVY -> config.getHeavyVehicleCrossingTime();
return config.getLightVehicleCrossingTime(); // default -> config.getLightVehicleCrossingTime();
case HEAVY: };
return config.getHeavyVehicleCrossingTime(); //
default:
return config.getLightVehicleCrossingTime(); //
}
} }
/** /**
* Requests the thread to stop gracefully (graceful shutdown). * Requests the thread to stop gracefully.
* Sets the 'running' flag to false. The thread will finish * Sets the running flag and interrupts the thread to unblock any sleep() calls.
* its current sleep cycle and exit the 'run()' loop.
*/ */
public void shutdown() { public void shutdown() {
this.running = false; this.running = false;
if (currentThread != null && currentThread.isAlive()) {
currentThread.interrupt();
}
} }
} }

View File

@@ -1,5 +1,5 @@
import java.io.IOException; import java.io.IOException;
import java.io.ObjectOutputStream; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -19,6 +19,7 @@ import sd.IntersectionProcess;
import sd.model.MessageType; import sd.model.MessageType;
import sd.model.Vehicle; import sd.model.Vehicle;
import sd.model.VehicleType; import sd.model.VehicleType;
import sd.protocol.SocketConnection;
/** /**
* Tests for IntersectionProcess - covers initialization, traffic lights, * Tests for IntersectionProcess - covers initialization, traffic lights,
@@ -187,7 +188,7 @@ public class IntersectionProcessTest {
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString()); intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
intersectionProcess.initialize(); intersectionProcess.initialize();
// start server in seperate thread // start server in separate thread
Thread serverThread = new Thread(() -> { Thread serverThread = new Thread(() -> {
try { try {
intersectionProcess.start(); intersectionProcess.start();
@@ -197,13 +198,22 @@ public class IntersectionProcessTest {
}); });
serverThread.start(); serverThread.start();
Thread.sleep(500); // wait for server to start // Wait for server to actually start with retries
boolean serverReady = false;
// try connecting to check if its running for (int i = 0; i < 20; i++) {
try (Socket clientSocket = new Socket("localhost", 18001)) { Thread.sleep(100);
assertTrue(clientSocket.isConnected()); try (Socket testSocket = new Socket()) {
testSocket.connect(new java.net.InetSocketAddress("localhost", 18001), 500);
serverReady = true;
break;
} catch (IOException e) {
// Server not ready yet, continue waiting
}
} }
assertTrue(serverReady, "Server should start and bind to port 18001");
// Shutdown immediately after confirming server is running
intersectionProcess.shutdown(); intersectionProcess.shutdown();
serverThread.join(2000); serverThread.join(2000);
} }
@@ -219,11 +229,17 @@ public class IntersectionProcessTest {
cr2.initialize(); cr2.initialize();
Thread thread1 = new Thread(() -> { Thread thread1 = new Thread(() -> {
try { cr1.start(); } catch (IOException e) { } try {
cr1.start();
} catch (IOException e) {
}
}); });
Thread thread2 = new Thread(() -> { Thread thread2 = new Thread(() -> {
try { cr2.start(); } catch (IOException e) { } try {
cr2.start();
} catch (IOException e) {
}
}); });
thread1.start(); thread1.start();
@@ -256,30 +272,32 @@ public class IntersectionProcessTest {
Thread serverThread = new Thread(() -> { Thread serverThread = new Thread(() -> {
try { try {
intersectionProcess.start(); intersectionProcess.start();
} catch (IOException e) { } } catch (IOException e) {
}
}); });
serverThread.start(); serverThread.start();
Thread.sleep(500); Thread.sleep(500);
// create test vehicle try {
// create test vehicle - FIXED: use 4-parameter constructor
java.util.List<String> route = Arrays.asList("Cr2", "Cr3", "S"); java.util.List<String> route = Arrays.asList("Cr2", "Cr3", "S");
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route); Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
// send vehicle from Cr1 to Cr2 // send vehicle from Cr1 to Cr2 - FIXED: use SocketConnection
try (Socket socket = new Socket("localhost", 18002)) { try (Socket socket = new Socket("localhost", 18002);
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); SocketConnection conn = new SocketConnection(socket)) {
TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle); TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle);
out.writeObject(message); conn.sendMessage(message);
out.flush();
Thread.sleep(1000); // wait for procesing Thread.sleep(1000); // wait for processing
} }
} finally {
intersectionProcess.shutdown(); intersectionProcess.shutdown();
serverThread.join(2000); serverThread.join(2000);
} }
}
// routing config tests // routing config tests
@@ -312,7 +330,8 @@ public class IntersectionProcessTest {
Thread serverThread = new Thread(() -> { Thread serverThread = new Thread(() -> {
try { try {
intersectionProcess.start(); intersectionProcess.start();
} catch (IOException e) { } } catch (IOException e) {
}
}); });
serverThread.start(); serverThread.start();
@@ -330,30 +349,35 @@ public class IntersectionProcessTest {
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString()); intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
intersectionProcess.initialize(); intersectionProcess.initialize();
// Start server in separate thread
Thread serverThread = new Thread(() -> { Thread serverThread = new Thread(() -> {
try { try {
intersectionProcess.start(); intersectionProcess.start();
} catch (IOException e) { } } catch (IOException e) {
// Expected on shutdown
}
}); });
serverThread.start(); serverThread.start();
// Wait for server to start
Thread.sleep(500); Thread.sleep(500);
// verify server running // Shutdown
try (Socket socket = new Socket("localhost", 18001)) {
assertTrue(socket.isConnected());
}
intersectionProcess.shutdown(); intersectionProcess.shutdown();
serverThread.join(2000); serverThread.join(2000);
// after shutdown conection should fail // Give shutdown time to complete
Thread.sleep(500); Thread.sleep(200);
Exception exception = assertThrows(IOException.class, () -> {
Socket socket = new Socket("localhost", 18001); // Verify we cannot connect (server socket is closed)
socket.close(); boolean connectionFailed = false;
}); try (Socket testSocket = new Socket()) {
assertNotNull(exception); testSocket.connect(new InetSocketAddress("localhost", 18001), 500);
} catch (IOException e) {
connectionFailed = true; // Expected - server should be closed
}
assertTrue(connectionFailed, "Server socket should be closed after shutdown");
} }
@Test @Test
@@ -365,7 +389,8 @@ public class IntersectionProcessTest {
Thread serverThread = new Thread(() -> { Thread serverThread = new Thread(() -> {
try { try {
intersectionProcess.start(); intersectionProcess.start();
} catch (IOException e) { } } catch (IOException e) {
}
}); });
serverThread.start(); serverThread.start();
@@ -388,20 +413,34 @@ public class IntersectionProcessTest {
@Test @Test
@Timeout(15) @Timeout(15)
public void testIntegration_TwoIntersectionsVehicleTransfer() throws IOException, InterruptedException { public void testIntegration_TwoIntersectionsVehicleTransfer() throws IOException, InterruptedException {
IntersectionProcess cr1 = null;
IntersectionProcess cr2 = null;
Thread thread1 = null;
Thread thread2 = null;
try {
// setup 2 intersections // setup 2 intersections
IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString()); cr1 = new IntersectionProcess("Cr1", configFile.toString());
IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString()); cr2 = new IntersectionProcess("Cr2", configFile.toString());
cr1.initialize(); cr1.initialize();
cr2.initialize(); cr2.initialize();
// start both // start both
Thread thread1 = new Thread(() -> { final IntersectionProcess cr1Final = cr1;
try { cr1.start(); } catch (IOException e) { } thread1 = new Thread(() -> {
try {
cr1Final.start();
} catch (IOException e) {
}
}); });
Thread thread2 = new Thread(() -> { final IntersectionProcess cr2Final = cr2;
try { cr2.start(); } catch (IOException e) { } thread2 = new Thread(() -> {
try {
cr2Final.start();
} catch (IOException e) {
}
}); });
thread1.start(); thread1.start();
@@ -409,25 +448,34 @@ public class IntersectionProcessTest {
Thread.sleep(1000); // wait for servers Thread.sleep(1000); // wait for servers
// send vehicle to Cr1 that goes to Cr2 // send vehicle to Cr1 that goes to Cr2 - FIXED: use 4-parameter constructor
java.util.List<String> route = Arrays.asList("Cr1", "Cr2", "S"); java.util.List<String> route = Arrays.asList("Cr1", "Cr2", "S");
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route); Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
try (Socket socket = new Socket("localhost", 18001)) { // FIXED: use SocketConnection
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); try (Socket socket = new Socket("localhost", 18001);
SocketConnection conn = new SocketConnection(socket)) {
TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle); TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle);
out.writeObject(message); conn.sendMessage(message);
out.flush();
Thread.sleep(2000); // time for processing Thread.sleep(2000); // time for processing
} }
} finally {
if (cr1 != null) {
cr1.shutdown(); cr1.shutdown();
}
if (cr2 != null) {
cr2.shutdown(); cr2.shutdown();
}
if (thread1 != null) {
thread1.join(2000); thread1.join(2000);
}
if (thread2 != null) {
thread2.join(2000); thread2.join(2000);
} }
}
}
@Test @Test
public void testMain_MissingArguments() { public void testMain_MissingArguments() {