feat(javaTest): replace route reservations atomically
This commit is contained in:
@@ -56,6 +56,98 @@ public final class CorridorReservationManager {
|
||||
return ReservationDecision.granted();
|
||||
}
|
||||
|
||||
/**
|
||||
* 原子替换一个活动任务尚未通过的走廊方向预约。
|
||||
*
|
||||
* <p>方法先把当前任务自己的旧预约视为可替换资源,对新路线做完整预检;任一新边与其他任务方向冲突时
|
||||
* 直接拒绝,旧预约保持原样。只有全部新边通过后才在同一次调用中提交移除和新增,因此重规划不会形成
|
||||
* “旧路线已释放、新路线尚未取得”的安全空窗。
|
||||
*
|
||||
* @param taskId 正在重规划的任务
|
||||
* @param newRoute 从车辆当前位置开始的新剩余路线
|
||||
* @param effectivePriority 当前饥饿保护后的有效优先级
|
||||
* @param arrivalMillis 任务到达队列的仿真时间
|
||||
* @return 替换是否成功以及失败时优先持有冲突方向的任务
|
||||
*/
|
||||
public ReservationDecision replaceRoute(String taskId, List<String> newRoute,
|
||||
int effectivePriority, long arrivalMillis) {
|
||||
if (taskId == null || taskId.trim().isEmpty()
|
||||
|| newRoute == null || newRoute.isEmpty()) {
|
||||
throw new IllegalArgumentException("task id and new route are required");
|
||||
}
|
||||
|
||||
List<EdgeRequest> requests = new ArrayList<EdgeRequest>();
|
||||
for (int i = 1; i < newRoute.size(); i++) {
|
||||
EdgeRequest request = request(newRoute.get(i - 1), newRoute.get(i));
|
||||
requests.add(request);
|
||||
EdgeReservation existing = edges.get(request.edgeId);
|
||||
if (existing == null || existing.direction == request.direction) continue;
|
||||
|
||||
// 当前任务旧预约可以被翻转;其他任务仍持有旧方向时,新路线不能提交。
|
||||
Set<String> otherHolders = new LinkedHashSet<String>(existing.holders);
|
||||
otherHolders.remove(taskId);
|
||||
if (!otherHolders.isEmpty()) {
|
||||
return ReservationDecision.denied(bestOwner(otherHolders),
|
||||
"CORRIDOR_DIRECTION_OWNED");
|
||||
}
|
||||
}
|
||||
|
||||
// 预检全部通过后才开始提交。先移除当前任务,绝不触碰同向共享该边的其他任务。
|
||||
Set<String> oldEdges = taskEdges.get(taskId);
|
||||
if (oldEdges != null) {
|
||||
for (String edgeId : new ArrayList<String>(oldEdges)) {
|
||||
EdgeReservation reservation = edges.get(edgeId);
|
||||
if (reservation == null) continue;
|
||||
reservation.holders.remove(taskId);
|
||||
if (reservation.holders.isEmpty()) edges.remove(edgeId);
|
||||
}
|
||||
}
|
||||
|
||||
Rank rank = new Rank(taskId, effectivePriority, arrivalMillis);
|
||||
ranks.put(taskId, rank);
|
||||
Set<String> replacementEdges = new LinkedHashSet<String>();
|
||||
for (EdgeRequest request : requests) {
|
||||
EdgeReservation reservation = edges.get(request.edgeId);
|
||||
if (reservation == null) {
|
||||
reservation = new EdgeReservation(request.direction);
|
||||
edges.put(request.edgeId, reservation);
|
||||
}
|
||||
reservation.holders.add(taskId);
|
||||
replacementEdges.add(request.edgeId);
|
||||
}
|
||||
if (replacementEdges.isEmpty()) {
|
||||
taskEdges.remove(taskId);
|
||||
} else {
|
||||
taskEdges.put(taskId, replacementEdges);
|
||||
}
|
||||
return ReservationDecision.granted();
|
||||
}
|
||||
|
||||
/**
|
||||
* 冻结当前全部走廊方向和持有任务,供路线质量评价和审计报告使用。
|
||||
*
|
||||
* @return 按物理边规范键排序的不可变预约列表
|
||||
*/
|
||||
public List<CorridorReservationSnapshot> snapshot() {
|
||||
List<String> edgeIds = new ArrayList<String>(edges.keySet());
|
||||
Collections.sort(edgeIds);
|
||||
List<CorridorReservationSnapshot> result =
|
||||
new ArrayList<CorridorReservationSnapshot>();
|
||||
for (String edgeId : edgeIds) {
|
||||
EdgeReservation reservation = edges.get(edgeId);
|
||||
int separator = edgeId.indexOf('|');
|
||||
String left = edgeId.substring(0, separator);
|
||||
String right = edgeId.substring(separator + 1);
|
||||
List<String> holders = new ArrayList<String>(reservation.holders);
|
||||
Collections.sort(holders);
|
||||
result.add(new CorridorReservationSnapshot(
|
||||
reservation.direction > 0 ? left : right,
|
||||
reservation.direction > 0 ? right : left,
|
||||
holders));
|
||||
}
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在车辆通过一条边后立即释放该段走廊,允许后续同向或反向任务重新竞争资源。
|
||||
* @param taskId 执行该处理所需的 task id 输入。
|
||||
@@ -207,6 +299,29 @@ public final class CorridorReservationManager {
|
||||
public String getReason() { return reason; }
|
||||
}
|
||||
|
||||
/** 一条物理走廊当前的有向预约及全部合法同向持有者。 */
|
||||
public static final class CorridorReservationSnapshot {
|
||||
private final String fromSite;
|
||||
private final String toSite;
|
||||
private final List<String> taskIds;
|
||||
|
||||
private CorridorReservationSnapshot(String fromSite, String toSite,
|
||||
List<String> taskIds) {
|
||||
this.fromSite = fromSite;
|
||||
this.toSite = toSite;
|
||||
this.taskIds = Collections.unmodifiableList(new ArrayList<String>(taskIds));
|
||||
}
|
||||
|
||||
/** 返回预约方向的起点。 */
|
||||
public String getFromSite() { return fromSite; }
|
||||
|
||||
/** 返回预约方向的终点。 */
|
||||
public String getToSite() { return toSite; }
|
||||
|
||||
/** 返回共享该方向预约的任务编号,按编号稳定排序。 */
|
||||
public List<String> getTaskIds() { return taskIds; }
|
||||
}
|
||||
|
||||
private static final class EdgeReservation {
|
||||
private final int direction;
|
||||
private final Set<String> holders = new LinkedHashSet<String>();
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.ft546.wcs.shadow.history.simulation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
@@ -50,4 +52,53 @@ class CorridorReservationManagerTest {
|
||||
.isGranted());
|
||||
assertEquals("FIRST", manager.ownerFor("A", "B"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectedReplacementKeepsEveryOldReservation() {
|
||||
CorridorReservationManager manager = new CorridorReservationManager();
|
||||
assertTrue(manager.reserve("TASK-1", Arrays.asList("A", "B", "C"), 1, 10L)
|
||||
.isGranted());
|
||||
assertTrue(manager.reserve("TASK-2", Arrays.asList("C", "D"), 2, 20L)
|
||||
.isGranted());
|
||||
|
||||
CorridorReservationManager.ReservationDecision denied = manager.replaceRoute(
|
||||
"TASK-1", Arrays.asList("A", "D", "C"), 1, 10L);
|
||||
|
||||
assertFalse(denied.isGranted());
|
||||
assertEquals(new LinkedHashSet<String>(Arrays.asList("A->B", "B->C")),
|
||||
edgesFor(manager, "TASK-1"));
|
||||
assertEquals("TASK-1", manager.ownerFor("A", "B"));
|
||||
assertEquals("TASK-1", manager.ownerFor("B", "C"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void successfulReplacementCommitsNewRouteAndPreservesSharedOldHolder() {
|
||||
CorridorReservationManager manager = new CorridorReservationManager();
|
||||
assertTrue(manager.reserve("TASK-1", Arrays.asList("A", "B", "C"), 1, 10L)
|
||||
.isGranted());
|
||||
assertTrue(manager.reserve("TASK-2", Arrays.asList("A", "B", "C"), 2, 20L)
|
||||
.isGranted());
|
||||
|
||||
CorridorReservationManager.ReservationDecision replaced = manager.replaceRoute(
|
||||
"TASK-1", Arrays.asList("A", "D", "C"), 1, 10L);
|
||||
|
||||
assertTrue(replaced.isGranted());
|
||||
assertEquals(new LinkedHashSet<String>(Arrays.asList("A->D", "D->C")),
|
||||
edgesFor(manager, "TASK-1"));
|
||||
assertEquals(new LinkedHashSet<String>(Arrays.asList("A->B", "B->C")),
|
||||
edgesFor(manager, "TASK-2"));
|
||||
assertEquals("TASK-2", manager.ownerFor("A", "B"));
|
||||
assertEquals("TASK-1", manager.ownerFor("A", "D"));
|
||||
}
|
||||
|
||||
private Set<String> edgesFor(CorridorReservationManager manager, String taskId) {
|
||||
Set<String> edges = new LinkedHashSet<String>();
|
||||
for (CorridorReservationManager.CorridorReservationSnapshot reservation
|
||||
: manager.snapshot()) {
|
||||
if (reservation.getTaskIds().contains(taskId)) {
|
||||
edges.add(reservation.getFromSite() + "->" + reservation.getToSite());
|
||||
}
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user