diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulator.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulator.java index 5f1527b..f6eefd7 100644 --- a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulator.java +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulator.java @@ -90,26 +90,33 @@ public final class HistoricalDispatchSimulator { clearanceMoves, executions, terminal, planner, assignment, plc, reservations, events); Set traversedEdges = new LinkedHashSet(); - advanceClearanceMoves(tick, now, input, config, vehicles, clearanceMoves, + ClearanceProgress clearanceProgress = advanceClearanceMoves( + tick, now, input, config, vehicles, clearanceMoves, ledger, plc, events, traversedEdges); - advanceActiveTasks(tick, now, input, config, queue, vehicles, + boolean businessProgress = advanceActiveTasks( + tick, now, input, config, queue, vehicles, active, executions, terminal, planner, ledger, plc, archive, wms, invariants, reservations, clearanceMoves, clearancePlanner, events, traversedEdges); verifyLedger(ledger, invariants); - if (watchdog.observe(terminal.size())) { + if (watchdog.observe(terminal.size(), + businessProgress || clearanceProgress.moved, + clearanceProgress.completed)) { invariants.deadlock(); - snapshotWaiting(queue, active, executions, terminal, plc, - "DEADLOCK_TIMEOUT", now); - break; + throw new IllegalStateException("simulation made no physical progress at tick " + + tick + ", terminal=" + terminal.size() + + ", queued=" + queue.size() + ", active=" + active.keySet() + + ", clearance=" + clearanceMoves.keySet()); } now += STEP_MILLIS; tick++; } if (terminal.size() < expected) { - snapshotWaiting(queue, active, executions, terminal, plc, - "TICK_LIMIT", now); + throw new IllegalStateException("simulation tick limit reached: tick=" + tick + + ", terminal=" + terminal.size() + ", expected=" + expected + + ", queued=" + queue.size() + ", active=" + active.keySet() + + ", clearance=" + clearanceMoves.keySet()); } List results = new ArrayList(terminal.values()); @@ -212,7 +219,6 @@ public final class HistoricalDispatchSimulator { terminal.put(task.getSimulationTaskId(), result(executions.get( task.getSimulationTaskId()), SimulationTaskStatus.FAILED, "NO_ROUTE", plc, now)); - progress = true; continue; } @@ -398,7 +404,6 @@ public final class HistoricalDispatchSimulator { current.execution.task.getSimulationTaskId(), vehicleId, "RETRY_EXHAUSTED_REDISPATCH", from, to)); } - progress = true; continue; } Site nextSite = input.getGridMap().getSite(to); @@ -421,7 +426,7 @@ public final class HistoricalDispatchSimulator { return progress; } - private void advanceClearanceMoves(long tick, long now, + private ClearanceProgress advanceClearanceMoves(long tick, long now, HistoricalSimulationInput input, SimulationConfig config, Map vehicles, @@ -430,6 +435,8 @@ public final class HistoricalDispatchSimulator { SimulatedPlcGateway plc, List events, Set traversedEdges) { + boolean moved = false; + boolean completed = false; List vehicleIds = new ArrayList(clearanceMoves.keySet()); Collections.sort(vehicleIds); for (String vehicleId : vehicleIds) { @@ -495,15 +502,18 @@ public final class HistoricalDispatchSimulator { ledger.release(vehicleId, Arrays.asList(ResourceKey.node(from), edge)); traversedEdges.add(direction); clearance.moved(); + moved = true; events.add(event(tick, now, "CLEARANCE_MOVE", clearance.getOwnerTaskId(), vehicleId, "ACK", from, to)); if (clearance.isComplete()) { clearanceMoves.remove(vehicleId); + completed = true; events.add(event(tick, now, "CLEARANCE_COMPLETE", clearance.getOwnerTaskId(), vehicleId, "ROUTE_CLEARED", to, to)); } } + return new ClearanceProgress(moved, completed); } private boolean planClearanceForIdleBlockers( @@ -714,4 +724,14 @@ public final class HistoricalDispatchSimulator { this.route = Collections.unmodifiableList(new ArrayList(route)); } } + + private static final class ClearanceProgress { + private final boolean moved; + private final boolean completed; + + private ClearanceProgress(boolean moved, boolean completed) { + this.moved = moved; + this.completed = completed; + } + } } diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/SimulationProgressWatchdog.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/SimulationProgressWatchdog.java index ef5bf66..7fac7bb 100644 --- a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/SimulationProgressWatchdog.java +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/SimulationProgressWatchdog.java @@ -14,10 +14,15 @@ public final class SimulationProgressWatchdog { } public boolean observe(int terminalCount) { + return observe(terminalCount, false, false); + } + + public boolean observe(int terminalCount, boolean vehicleMoved, + boolean clearanceCompleted) { if (terminalCount < lastTerminalCount) { throw new IllegalArgumentException("terminal count must be monotonic"); } - if (terminalCount > lastTerminalCount) { + if (terminalCount > lastTerminalCount || vehicleMoved || clearanceCompleted) { lastTerminalCount = terminalCount; unchangedEvents = 0; return false; diff --git a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulationRealDataTest.java b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulationRealDataTest.java index dc85da8..757a756 100644 --- a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulationRealDataTest.java +++ b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulationRealDataTest.java @@ -18,12 +18,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class HistoricalDispatchSimulationRealDataTest { private static HistoricalDispatchSimulationResult result; private static HistoricalDispatchSimulationResult secondRun; + private static HistoricalSimulationInput simulationInput; @BeforeAll static void simulateRealHistoryTwice() { HistoricalQueueDataset dataset = new HistoricalQueueLoader().load( HistoricalDispatchSimulationRealDataTest.class.getClassLoader(), "shadow/history"); HistoricalSimulationInput input = new HistoricalSimulationInputFactory().create(dataset); + simulationInput = input; DeterministicFaultPlan.Builder faults = DeterministicFaultPlan.builder(); for (int i = 0; i < input.getTasks().size(); i += 37) { faults.plcFailure(input.getTasks().get(i).getSimulationTaskId(), 1, SEND_FAILURE); @@ -46,22 +48,30 @@ class HistoricalDispatchSimulationRealDataTest { assertNotNull(task.getRecord().getTaskNo()); assertNotNull(task.getRecord().getStartSite()); assertNotNull(task.getRecord().getEndSite()); + for (int i = 1; i < task.getPlannedPath().size(); i++) { + assertTrue(simulationInput.getGridMap() + .getNeighbors(task.getPlannedPath().get(i - 1)) + .contains(task.getPlannedPath().get(i))); + } } @Test void completesTheRealWorkloadDeterministicallyWithoutSafetyViolations() { assertEquals(240, result.getTaskResults().size()); - assertEquals(240, result.getCompletedCount() - + result.getWaitingCount() + result.getFailedCount()); - assertTrue(result.getCompletedCount() > 0); - if (result.getWaitingCount() > 0) { - assertTrue(result.getInvariants().getDeadlocks() > 0); - } + assertEquals(240, result.getCompletedCount()); + assertEquals(0, result.getWaitingCount()); + assertEquals(0, result.getFailedCount()); assertTrue(result.getReassignmentCount() > 0); assertTrue(result.getRetryCount() > 0); - assertEquals(result.getCompletedCount(), result.getArchiveCount()); - assertEquals(result.getCompletedCount(), result.getWmsBusinessEffects()); - assertEquals(result.getCompletedCount() * 2, result.getWmsDeliveryAttempts()); + assertEquals(240, result.getArchiveCount()); + assertEquals(240, result.getWmsBusinessEffects()); + assertEquals(480, result.getWmsDeliveryAttempts()); + assertEquals(0, result.getInvariants().getDeadlocks()); + assertEquals(0, result.getInvariants().getCapacityViolations()); + assertEquals(0, result.getInvariants().getNodeDoubleOccupancyViolations()); + assertEquals(0, result.getInvariants().getOppositeEdgeViolations()); + assertEquals(0, result.getInvariants().getDuplicateArchiveAttempts()); + assertEquals(0, result.getInvariants().getDuplicateWmsBusinessEffects()); assertEquals(0, result.getInvariants().totalSafetyViolations()); assertEquals(result.canonicalView(), secondRun.canonicalView()); } diff --git a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/SimulationProgressWatchdogTest.java b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/SimulationProgressWatchdogTest.java index f1b4e0d..fe4a7c0 100644 --- a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/SimulationProgressWatchdogTest.java +++ b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/SimulationProgressWatchdogTest.java @@ -24,4 +24,20 @@ class SimulationProgressWatchdogTest { assertFalse(watchdog.observe(1)); assertTrue(watchdog.observe(1)); } + + @Test + void resetsOnlyForTerminalMovementOrCompletedClearanceProgress() { + SimulationProgressWatchdog watchdog = new SimulationProgressWatchdog(3, 0); + + assertFalse(watchdog.observe(0, false, false)); + assertFalse(watchdog.observe(0, false, false)); + assertFalse(watchdog.observe(0, true, false)); + assertFalse(watchdog.observe(0, false, false)); + assertFalse(watchdog.observe(0, false, true)); + assertFalse(watchdog.observe(0, false, false)); + assertFalse(watchdog.observe(1, false, false)); + assertFalse(watchdog.observe(1, false, false)); + assertFalse(watchdog.observe(1, false, false)); + assertTrue(watchdog.observe(1, false, false)); + } }