fix(lastleg): read committed order command replays after lock waits
This commit is contained in:
@@ -372,7 +372,7 @@ export async function verifyReceivedOrderHold(context) {
|
||||
&& originalBalances.every(row=>id(row.customerId)===customerId && id(row.overseasWarehouseId)===warehouseId)
|
||||
&& originalPositions.every(row=>id(row.customerId)===customerId && id(row.overseasWarehouseId)===warehouseId))
|
||||
const proof = owned.holdInventoryProof = { marker:context.marker, orderId, customerId, warehouseId,
|
||||
balanceIds, positionIds, snapshots:{}, status:'RUNNING', restored:false }
|
||||
balanceIds, positionIds, snapshots:{}, concurrentCommands:[], status:'RUNNING', restored:false }
|
||||
const orderPath = query(`${ORDERS}/${encodeURIComponent(orderId)}/hold`, {customerId})
|
||||
const inventoryPath = '/api/platform/v1/last-leg/inventory'
|
||||
let uncertain = false, otherApplied = false, active = null, baseline, otherHeld, otherBody
|
||||
@@ -419,14 +419,24 @@ export async function verifyReceivedOrderHold(context) {
|
||||
}
|
||||
const stock = value => ({balances:value.balances.map(holdStableStock),positions:value.positions.map(holdStableStock)})
|
||||
const expectStock = (actual,expected,label) => require(label,sameHoldFacts(stock(actual),stock(expected)))
|
||||
const orderCommand = async (held,version,suffix) => {
|
||||
const orderCommand = async (held,version,suffix,copies=1) => {
|
||||
const body={held,chargeable:held,reason:`${context.marker} ${suffix}`,expectedVersion:Number(version)}
|
||||
const headers={'Idempotency-Key':`${attemptKey}-${suffix}`}
|
||||
const data=await request('PUT',held?'暂扣本轮单票尾程订单':'解除本轮单票尾程暂扣',orderPath,body,headers)
|
||||
// Settle both writes before inspecting or recovering: one rejected promise does not
|
||||
// mean its sibling has stopped modifying the order.
|
||||
const outcomes=await Promise.allSettled(Array.from({length:copies},()=>
|
||||
request('PUT',held?'暂扣本轮单票尾程订单':'解除本轮单票尾程暂扣',orderPath,body,headers)))
|
||||
const failed=outcomes.find(result=>result.status==='rejected')
|
||||
if(failed) { if(copies>1) uncertain=true;throw failed.reason }
|
||||
const replies=outcomes.map(result=>result.value),data=replies[0]
|
||||
// A malformed successful response is not sufficient authority for a recovery mutation.
|
||||
if(id(data?.orderId)!==orderId || data.held!==held || Number(data.version)!==Number(version)+1) uncertain=true
|
||||
require('暂扣命令返回本订单预期版本及计费状态',!uncertain && data.chargeable===held && (held || data.reason==null))
|
||||
active=held?{version:Number(data.version),reason:body.reason}:null
|
||||
if(copies>1) {
|
||||
proof.concurrentCommands.push({held,copies,expectedVersion:Number(version),replies})
|
||||
require('并发同键命令返回相同订单状态和唯一新版本',replies.every(reply=>sameHoldFacts(reply,data)))
|
||||
}
|
||||
return {body,headers,data}
|
||||
}
|
||||
const releaseOther = async () => {
|
||||
@@ -461,7 +471,7 @@ export async function verifyReceivedOrderHold(context) {
|
||||
const allHeld=shiftHoldStock(otherHeld,null,true)
|
||||
for(let cycle=1;cycle<=2;cycle++) {
|
||||
const before=await read(`C${cycle}_BEFORE`)
|
||||
const held=await orderCommand(true,before.order.version,`F32-C${cycle}-HOLD`)
|
||||
const held=await orderCommand(true,before.order.version,`F32-C${cycle}-HOLD`,cycle===1?2:1)
|
||||
const during=await read(`C${cycle}_HELD`)
|
||||
require('暂扣只推进单票版本并保留计费原因',during.order.detained===true && Number(during.order.version)===Number(held.data.version)
|
||||
&& during.order.holdReason===held.body.reason && during.order.heldChargeable===true)
|
||||
@@ -473,7 +483,7 @@ export async function verifyReceivedOrderHold(context) {
|
||||
await reject('暂扣尾程订单不能生成人工计划',`${PLANS}/manual`,manualPlanBody(representative,held.data.version,context.marker),
|
||||
{'Idempotency-Key':`${attemptKey}-F32-C${cycle}-PLAN`},'POST')
|
||||
require('人工计划拒绝后暂扣与库存不变',sameHoldFacts(await read(`C${cycle}_PLAN_REJECTED`),during))
|
||||
const released=await orderCommand(false,during.order.version,`F32-C${cycle}-RELEASE`)
|
||||
const released=await orderCommand(false,during.order.version,`F32-C${cycle}-RELEASE`,cycle===1?2:1)
|
||||
const after=await read(`C${cycle}_RELEASED`)
|
||||
require('解除暂扣清空计费与原因并推进版本',after.order.detained===false && after.order.heldChargeable===false
|
||||
&& after.order.holdReason==null && Number(after.order.version)===Number(released.data.version))
|
||||
|
||||
Reference in New Issue
Block a user