diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java index a2bef3a..ee95058 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -12,10 +12,10 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import sd.config.SimulationConfig; +import sd.engine.TrafficLightThread; import sd.model.Intersection; import sd.model.MessageType; import sd.model.TrafficLight; -import sd.model.TrafficLightState; import sd.model.Vehicle; import sd.protocol.MessageProtocol; import sd.protocol.SocketConnection; @@ -178,128 +178,15 @@ public class IntersectionProcess { System.out.println("\n[" + intersectionId + "] Starting traffic light threads..."); for (TrafficLight light : intersection.getTrafficLights()) { - trafficLightPool.submit(() -> runTrafficLightCycle(light)); + + TrafficLightThread lightTask = new TrafficLightThread(light, this, config); + + trafficLightPool.submit(lightTask); + System.out.println(" Started thread for: " + light.getDirection()); } } - /** - * The main loop for a traffic light thread. - * Continuously cycles between green and red states. - * - * only one traffic light can be green at any given time in this intersection. - * - * @param light The traffic light to control. - */ - private void runTrafficLightCycle(TrafficLight light) { - System.out.println("[" + light.getId() + "] Traffic light thread started."); - - while (running) { - try { - // Acquire coordination lock to become green - trafficCoordinationLock.lock(); - try { - // Wait until no other direction is green - while (currentGreenDirection != null && running) { - trafficCoordinationLock.unlock(); - Thread.sleep(100); // Brief wait before retrying - trafficCoordinationLock.lock(); - } - - if (!running) { - break; // Exit if shutting down - } - - // Mark this direction as the current green light - currentGreenDirection = light.getDirection(); - light.changeState(TrafficLightState.GREEN); - System.out.println("[" + light.getId() + "] State: GREEN"); - - } finally { - trafficCoordinationLock.unlock(); - } - - // Process vehicles while green - processGreenLight(light); - - // Wait for green duration - Thread.sleep((long) (light.getGreenTime() * 1000)); - - // Release coordination lock (turn red) - trafficCoordinationLock.lock(); - try { - light.changeState(TrafficLightState.RED); - currentGreenDirection = null; // Release exclusive access - System.out.println("[" + light.getId() + "] State: RED (RELEASED ACCESS)"); - } finally { - trafficCoordinationLock.unlock(); - } - - // Wait for red duration - Thread.sleep((long) (light.getRedTime() * 1000)); - - } catch (InterruptedException e) { - System.out.println("[" + light.getId() + "] Traffic light thread interrupted."); - break; - } - } - - System.out.println("[" + light.getId() + "] Traffic light thread stopped."); - } - - /** - * Processes vehicles when a traffic light is GREEN. - * Dequeues vehicles and sends them to their next destination. - * - * @param light The traffic light that is currently green. - */ - private void processGreenLight(TrafficLight light) { - while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) { - Vehicle vehicle = light.removeVehicle(); - - if (vehicle != null) { - // Get crossing time based on vehicle type - double crossingTime = getCrossingTimeForVehicle(vehicle); - - // Simulate crossing time - try { - Thread.sleep((long) (crossingTime * 1000)); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - - // Update vehicle statistics - vehicle.addCrossingTime(crossingTime); - - // Update intersection statistics - intersection.incrementVehiclesSent(); - - // Send vehicle to next destination - sendVehicleToNextDestination(vehicle); - } - } - } - - /** - * Gets the crossing time for a vehicle based on its type. - * - * @param vehicle The vehicle. - * @return The crossing time in seconds. - */ - private double getCrossingTimeForVehicle(Vehicle vehicle) { - switch (vehicle.getType()) { - case BIKE: - return config.getBikeVehicleCrossingTime(); - case LIGHT: - return config.getLightVehicleCrossingTime(); - case HEAVY: - return config.getHeavyVehicleCrossingTime(); - default: - return config.getLightVehicleCrossingTime(); - } - } - /** * Sends a vehicle to its next destination via socket connection. * @@ -410,12 +297,21 @@ public class IntersectionProcess { System.out.println("[" + intersectionId + "] New connection accepted from " + clientSocket.getInetAddress().getHostAddress()); - // Check running flag again before handling - prevents accepting during shutdown + // Check running flag again before handling if (!running) { clientSocket.close(); break; } + // **Set timeout before submitting to handler** + try { + clientSocket.setSoTimeout(1000); + } catch (java.net.SocketException e) { + System.err.println("[" + intersectionId + "] Failed to set timeout: " + e.getMessage()); + clientSocket.close(); + continue; + } + // Handle each connection in a separate thread connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket)); @@ -424,7 +320,6 @@ public class IntersectionProcess { if (!running) { break; // Normal shutdown } - // Unexpected error during normal operation System.err.println("[" + intersectionId + "] Error accepting connection: " + e.getMessage()); } @@ -438,11 +333,16 @@ public class IntersectionProcess { * @param clientSocket The accepted socket connection. */ private void handleIncomingConnection(Socket clientSocket) { - try (SocketConnection connection = new SocketConnection(clientSocket)) { - - // Set socket timeout so receiveMessage() won't block forever + try { clientSocket.setSoTimeout(1000); // 1 second timeout + } catch (java.net.SocketException e) { + System.err.println("[" + intersectionId + "] Failed to set socket timeout: " + e.getMessage()); + return; + } + + try (SocketConnection connection = new SocketConnection(clientSocket)) { + System.out.println("[" + intersectionId + "] New connection accepted from " + clientSocket.getInetAddress().getHostAddress()); @@ -462,7 +362,7 @@ public class IntersectionProcess { } } catch (java.net.SocketTimeoutException e) { - // Timeout is expected - just check running flag and continue + // Timeout - check running flag and continue if (!running) { break; } @@ -487,46 +387,57 @@ public class IntersectionProcess { * Shuts down all threads and closes all connections. */ public void shutdown() { + // Check if already shutdown + if (!running) { + return; // Already shutdown, do nothing + } + System.out.println("\n[" + intersectionId + "] Shutting down..."); running = false; + // 1. Close ServerSocket first if (serverSocket != null && !serverSocket.isClosed()) { try { serverSocket.close(); } catch (IOException e) { - System.err.println("[" + intersectionId + "] Error closing server socket: " + - e.getMessage()); + // Expected } } + + // 2. Shutdown thread pools with force + if (trafficLightPool != null && !trafficLightPool.isShutdown()) { + trafficLightPool.shutdownNow(); + } + if (connectionHandlerPool != null && !connectionHandlerPool.isShutdown()) { + connectionHandlerPool.shutdownNow(); + } + + // 3. Wait briefly for termination (don't block forever) + try { + if (trafficLightPool != null) { + trafficLightPool.awaitTermination(1, TimeUnit.SECONDS); + } + if (connectionHandlerPool != null) { + connectionHandlerPool.awaitTermination(1, TimeUnit.SECONDS); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + // 4. Close outgoing connections synchronized (outgoingConnections) { for (SocketConnection conn : outgoingConnections.values()) { try { conn.close(); } catch (Exception e) { - // Ignore errors during shutdown + // Ignore } } outgoingConnections.clear(); } - trafficLightPool.shutdown(); - connectionHandlerPool.shutdownNow(); // Use shutdownNow() to interrupt running tasks - - try { - if (!trafficLightPool.awaitTermination(2, TimeUnit.SECONDS)) { - trafficLightPool.shutdownNow(); - } - if (!connectionHandlerPool.awaitTermination(2, TimeUnit.SECONDS)) { - connectionHandlerPool.shutdownNow(); - } - } catch (InterruptedException e) { - trafficLightPool.shutdownNow(); - connectionHandlerPool.shutdownNow(); - Thread.currentThread().interrupt(); - } - System.out.println("[" + intersectionId + "] Shutdown complete."); - System.out.println("=".repeat(60) + "\n"); + System.out.println("============================================================\n"); } /** diff --git a/main/src/test/java/IntersectionProcessTest.java b/main/src/test/java/IntersectionProcessTest.java index 2b5ee08..cdb490c 100644 --- a/main/src/test/java/IntersectionProcessTest.java +++ b/main/src/test/java/IntersectionProcessTest.java @@ -97,11 +97,17 @@ public class IntersectionProcessTest { Files.writeString(configFile, configContent); } - // cleanup after tests @AfterEach public void tearDown() { if (intersectionProcess != null) { - intersectionProcess.shutdown(); + try { + // Only shutdown if still running + intersectionProcess.shutdown(); + } catch (Exception e) { + System.err.println("Error in tearDown: " + e.getMessage()); + } finally { + intersectionProcess = null; + } } }