fix(lastleg): read committed order command replays after lock waits

This commit is contained in:
2026-10-03 12:13:34 +08:00
parent 34cac9c42a
commit eadb431f33
6 changed files with 100 additions and 8 deletions
@@ -137,6 +137,32 @@ class InventoryHoldConcurrencyPersistenceTest {
assertEquals(12,amount("available_packages")+amount("hold_packages")+amount("allocated_packages")+amount("picked_packages")+shipped);
assertEquals(shipped,jdbc.queryForObject("SELECT COALESCE(-SUM(delta_packages),0) FROM ll_inventory_ledger WHERE business_type='SHIP'",Long.class));
}
@Test void simultaneousDuplicateHoldAndReleaseAdvanceOnceAndKeepIndependentHold() throws Exception {
inventory.hold("C1","operator",move(22,2,"OTHER-HOLD"));
ExecutorService pool=Executors.newFixedThreadPool(2);
try {
for (boolean held : new boolean[]{true,false}) {
int version=held?0:1;
String key=held?"parallel-hold":"parallel-release";
var ready=new CountDownLatch(2);var start=new CountDownLatch(1);
Callable<LastlegOrderCommandDtos.HoldResponse> command=()->{
ready.countDown();await(start);
return orders.hold("C1",11,key,new LastlegOrderCommandDtos.HoldRequest(held,false,"quality",version),"operator","Operator");
};
var first=pool.submit(command);var second=pool.submit(command);
await(ready);start.countDown();
var response=first.get(8,TimeUnit.SECONDS);
assertEquals(response,second.get(8,TimeUnit.SECONDS));
assertEquals(version+1,response.version());
assertEquals(version+1,orderMapper.selectById(11L).getVersion());
}
assertEquals(2,count("ll_order_command_record"));
assertEquals(2,count("ll_order_hold_history"));
assertEquals(5,count("ll_inventory_operation"));
assertEquals(5,count("ll_inventory_ledger"));
assertEquals(10,amount("available_packages"));assertEquals(2,amount("hold_packages"));conserved(0);
} finally {pool.shutdownNow();assertTrue(pool.awaitTermination(10,TimeUnit.SECONDS));}
}
@Test void secondHoldCycleMustMoveInventoryAgainAndNotReuseTheFirstCycle() {
hold(true,0,"hold-one"); hold(false,1,"release-one"); hold(true,2,"hold-two");
assertEquals(0,amount("available_packages")); assertEquals(12,amount("hold_packages"));
@@ -66,6 +66,18 @@ class LastlegOrderCommandServiceTest {
ReflectionTestUtils.setField(service, "objectMapper", JsonMapper.builder().findAndAddModules().build());
}
@Test
void commandTransactionsReadCommittedAfterWaitingForTheOrderLock() throws Exception {
for (var entry : List.of(
new Object[]{"hold", LastlegOrderCommandDtos.HoldRequest.class},
new Object[]{"saveManualNodes", LastlegOrderCommandDtos.ManualNodesRequest.class})) {
var method=LastlegOrderCommandService.class.getMethod((String)entry[0],String.class,long.class,
String.class,(Class<?>)entry[1],String.class,String.class);
assertEquals(org.springframework.transaction.annotation.Isolation.READ_COMMITTED,
method.getAnnotation(org.springframework.transaction.annotation.Transactional.class).isolation());
}
}
@Test
void holdsOnlySelectedOrderWithVersionAndImmutableHistory() {
LastlegOrder order = order(11L, 3);