perf(lastleg): limit transport reconciliation facts and trace sync phases
This commit is contained in:
+71
-2
@@ -207,16 +207,24 @@ class AppointmentWorkSyncServiceTest {
|
||||
assertThrows(ResponseStatusException.class,()->f.sync.synchronize(new SyncRequest("10",10)));
|
||||
assertFalse(f.guard.isInitialized());
|
||||
}
|
||||
/** 首轮只取运输事实;关联与解绑均保留原成员,第二轮仍观察全部活跃工作。 */
|
||||
@Test void sharedTripAcrossTwoContainersIsPersistedOnceAndKeepsBothContainers() {
|
||||
var f=new WorkTestFixture();
|
||||
var replaced=f.state("old",Type.TRANSPORT,Flow.TRANSFER,"old-requirement");
|
||||
replaced.setRetired(true);replaced.setRetirementReason("BATCH_REPLACED");f.add(replaced);
|
||||
record ScopeItem(String key,Type type,String trip,boolean retired,String reason) { }
|
||||
var scopes=new ArrayList<List<ScopeItem>>();
|
||||
var linked=new java.util.concurrent.atomic.AtomicBoolean(true);
|
||||
var sources=List.of("1","2").stream().map(id->new ContainerSource(id,"柜"+id,Instant.parse("2026-10-01T00:00:00Z"),null,null,"v1",Flow.TRANSFER,"America/Los_Angeles",
|
||||
List.of(new Requirement("R"+id,"B"+id,"AP"+id,"FBA","海外仓","ONT8","America/Los_Angeles",List.of(),List.of(),List.of())))).toList();
|
||||
when(f.gateway.discover(null,10)).thenReturn(new SourcePage(sources,null,true,"scope"));
|
||||
when(f.gateway.facts(anyList())).thenAnswer(call->{
|
||||
var requested=call.<List<AppointmentWorkState>>getArgument(0);
|
||||
scopes.add(requested.stream().map(s->new ScopeItem(s.getWorkItemKey(),s.getType(),s.getTripKey(),s.isRetired(),s.getRetirementReason())).toList());
|
||||
var values=new HashMap<String,Fact>();
|
||||
for(var state:call.<List<AppointmentWorkState>>getArgument(0)) if(state.getType()!=Type.ROUTE) {
|
||||
for(var state:requested) if(state.getType()!=Type.ROUTE) {
|
||||
if(state.getType()==Type.TRANSPORT) values.put(state.getWorkItemKey(),new Fact(state.getWorkItemKey(),"trip-v1","trip-impact-v1",true,PlanStatus.CONFIRMED,ExecutionStatus.PENDING,
|
||||
slot("2026-10-16"),slot("2026-10-16"),null,"America/Los_Angeles","TR-1","trip-1",List.of(AppointmentWorkPolicy.ORDER_READ),List.of(),null,List.of(),List.of(),List.of(),null,null));
|
||||
slot("2026-10-16"),slot("2026-10-16"),null,"America/Los_Angeles",linked.get()?"TR-1":null,linked.get()?"trip-1":null,List.of(AppointmentWorkPolicy.ORDER_READ),List.of(),null,List.of(),List.of(),List.of(),null,null));
|
||||
else values.put(state.getWorkItemKey(),fact(state,null,null,ExecutionStatus.PENDING,null));
|
||||
}
|
||||
return values;
|
||||
@@ -225,5 +233,66 @@ class AppointmentWorkSyncServiceTest {
|
||||
var transports=f.store.all().stream().filter(s->!s.isRetired() && s.getType()==Type.TRANSPORT).toList();
|
||||
assertEquals(1,transports.size()); assertEquals(2,transports.get(0).getContainers().size()); assertEquals(List.of("B1","B2"),transports.get(0).getBatchKeys());
|
||||
assertEquals(9,f.store.all().stream().filter(s->!s.isRetired()).count());
|
||||
assertEquals(4,scopes.size());
|
||||
for(int index:List.of(0,2)) {
|
||||
assertEquals(2,scopes.get(index).size());
|
||||
assertTrue(scopes.get(index).stream().allMatch(s->s.type()==Type.TRANSPORT && s.trip()==null && !"BATCH_REPLACED".equals(s.reason())));
|
||||
}
|
||||
assertTrue(scopes.get(2).stream().allMatch(s->s.retired() && "TRIP_LINKED".equals(s.reason())));
|
||||
var activeKeys=f.store.all().stream().filter(s->!s.isRetired()).map(AppointmentWorkState::getWorkItemKey).collect(java.util.stream.Collectors.toSet());
|
||||
for(int index:List.of(1,3)) assertEquals(activeKeys,scopes.get(index).stream().map(ScopeItem::key).collect(java.util.stream.Collectors.toSet()));
|
||||
linked.set(false);scopes.clear();
|
||||
f.sync.synchronize(new SyncRequest(null,10));
|
||||
assertEquals(2,scopes.size());assertEquals(2,scopes.get(0).size());
|
||||
assertTrue(scopes.get(0).stream().allMatch(s->s.type()==Type.TRANSPORT && s.retired() && "TRIP_LINKED".equals(s.reason())));
|
||||
var restored=f.store.all().stream().filter(s->!s.isRetired() && s.getType()==Type.TRANSPORT).toList();
|
||||
assertEquals(2,restored.size());assertTrue(restored.stream().allMatch(s->s.getTripKey()==null && s.getReplacementKeys().isEmpty()));
|
||||
assertEquals("TRIP_UNLINKED",f.store.get(transports.get(0).getWorkItemKey()).getRetirementReason());
|
||||
assertEquals(f.store.all().stream().filter(s->!s.isRetired()).map(AppointmentWorkState::getWorkItemKey).collect(java.util.stream.Collectors.toSet()),
|
||||
scopes.get(1).stream().map(ScopeItem::key).collect(java.util.stream.Collectors.toSet()));
|
||||
assertTrue(f.store.get(replaced.getWorkItemKey()).isRetired());
|
||||
}
|
||||
|
||||
/** 任一事实阶段失败均不得发布完整范围;此模拟验证发布门禁,不声称数据库回滚。 */
|
||||
@Test void failureInEitherFactRoundDoesNotPublishSynchronizedScope() {
|
||||
for(int failedRound:List.of(1,2)) {
|
||||
var f=new WorkTestFixture();f.guard.setInitialized(false);f.guard.setRequirementVersion(7);f.guard.setSynchronizedRequirementVersion(6);
|
||||
var transport=f.state("1",Type.TRANSPORT,Flow.TRANSFER,"r");f.add(transport);
|
||||
var pickup=f.state("1",Type.PICKUP,Flow.TRANSFER,null);f.add(pickup);
|
||||
var calls=new java.util.concurrent.atomic.AtomicInteger();
|
||||
when(f.gateway.facts(anyList())).thenAnswer(call->{
|
||||
var requested=call.<List<AppointmentWorkState>>getArgument(0);int round=calls.incrementAndGet();
|
||||
if(round==1) assertEquals(List.of(transport.getWorkItemKey()),requested.stream().map(AppointmentWorkState::getWorkItemKey).toList());
|
||||
else assertTrue(requested.stream().anyMatch(s->s.getWorkItemKey().equals(pickup.getWorkItemKey())));
|
||||
if(round==failedRound) throw AppointmentWorkPolicy.unavailable("原业务事实暂不可用");
|
||||
return Map.of(transport.getWorkItemKey(),fact(transport,null,null,ExecutionStatus.PENDING,null));
|
||||
});
|
||||
assertThrows(ResponseStatusException.class,()->f.sync.synchronize(new SyncRequest(null,10)));
|
||||
assertEquals(failedRound,calls.get());assertFalse(f.guard.isInitialized());
|
||||
assertEquals(6,f.guard.getSynchronizedRequirementVersion());assertNull(f.guard.getCompletedAt());
|
||||
verify(f.mapper,never()).saveGuard(any());
|
||||
}
|
||||
}
|
||||
|
||||
/** 慢事实失败保留原异常,只记录一次固定阶段摘要,不输出异常业务内容。 */
|
||||
@Test void slowFactFailureLogsOnePhaseSummaryWithoutPublishingOrLeakingFailure() {
|
||||
var f=new WorkTestFixture();var calls=new java.util.concurrent.atomic.AtomicInteger();
|
||||
var failure=AppointmentWorkPolicy.unavailable("private-source-detail-must-not-be-logged");
|
||||
when(f.gateway.facts(anyList())).thenAnswer(call->{
|
||||
if(calls.incrementAndGet()==1) return Map.of();
|
||||
Thread.sleep(270);throw failure;
|
||||
});
|
||||
var logger=(ch.qos.logback.classic.Logger)org.slf4j.LoggerFactory.getLogger(AppointmentWorkSyncService.class);
|
||||
var appender=new ch.qos.logback.core.read.ListAppender<ch.qos.logback.classic.spi.ILoggingEvent>();
|
||||
appender.start();logger.addAppender(appender);
|
||||
try {
|
||||
assertSame(failure,assertThrows(ResponseStatusException.class,()->f.sync.synchronize(new SyncRequest(null,10))));
|
||||
verify(f.mapper,never()).saveGuard(any());
|
||||
assertEquals(1,appender.list.size());var event=appender.list.get(0);
|
||||
assertEquals(ch.qos.logback.classic.Level.WARN,event.getLevel());
|
||||
assertTrue(event.getFormattedMessage().contains("failedStage=ACTIVE_FACTS"));
|
||||
assertTrue(event.getFormattedMessage().contains("transportFactsCount=0, activeFactsCount=0"));
|
||||
assertFalse(event.getFormattedMessage().contains("private-source-detail"));assertNull(event.getThrowableProxy());
|
||||
} finally { logger.detachAppender(appender);appender.stop(); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user