fix(delivery): msg 队头失败或退避只暂停同 FLID,不阻塞其他航班(ACM2-95)
- drainKafkaMsg 按分区键维护本轮暂停集:队头失败/退避未到期仅停该 FLID(D2) - 同一 FLID 内仍严格按 EVENT_ID 保序不越队 - 补三条用例:失败仅停同 FLID、退避未到期仅停同 FLID、同 FLID 跨轮保序(TestClocks 无 sleep)
This commit is contained in:
@@ -214,6 +214,82 @@ class DispatcherTickTest {
|
||||
d.tick()
|
||||
assertEquals(1, port.sent.count { it.topic == "msg" && it.key == "F9" })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `msg head failure pauses only the same FLID while other flights deliver in the same tick`() {
|
||||
val repo = StubMsgEvents()
|
||||
val port = FailingMsgKeyPort("F1")
|
||||
val d = dispatcher(repo, port)
|
||||
repo.insertAll(
|
||||
listOf(
|
||||
ev(1, Targets.KAFKA_MSG, "F1", """{"flid":"F1","v":1}"""),
|
||||
ev(2, Targets.KAFKA_MSG, "F2", """{"flid":"F2","v":1}"""),
|
||||
ev(3, Targets.KAFKA_MSG, "F1", """{"flid":"F1","v":2}"""),
|
||||
),
|
||||
)
|
||||
|
||||
d.tick()
|
||||
|
||||
// D2:F1 队头失败只暂停 F1(含 id=3 的后续),F2 照常发出
|
||||
assertEquals(1, port.sent.count { it.topic == "msg" && it.key == "F2" })
|
||||
assertEquals(0, port.sent.count { it.topic == "msg" && it.key == "F1" })
|
||||
val f1Rows = repo.rows.values.filter { it.partitionKey == "F1" }
|
||||
assertEquals(2, f1Rows.size)
|
||||
assertTrue(f1Rows.all { it.state == EventStatus.PENDING })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `msg head backoff not yet due pauses only the same FLID`() {
|
||||
val repo = StubMsgEvents()
|
||||
val port = StubDeliveryPort()
|
||||
val d = dispatcher(repo, port)
|
||||
repo.insertAll(
|
||||
listOf(
|
||||
ev(1, Targets.KAFKA_MSG, "F1", """{"flid":"F1","v":1}"""),
|
||||
ev(2, Targets.KAFKA_MSG, "F2", """{"flid":"F2","v":1}"""),
|
||||
),
|
||||
)
|
||||
repo.scheduleRetry(1, clock.instant().plusSeconds(60), 1) // F1 队头退避未到期
|
||||
|
||||
d.tick()
|
||||
|
||||
assertEquals(1, port.sent.count { it.topic == "msg" && it.key == "F2" })
|
||||
val f1 = repo.rows.values.single { it.partitionKey == "F1" }
|
||||
assertEquals(EventStatus.PENDING, f1.state)
|
||||
assertEquals(1, f1.attempts)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `same FLID msg events keep strict order across ticks`() {
|
||||
val repo = StubMsgEvents()
|
||||
val port = StubDeliveryPort()
|
||||
val d = dispatcher(repo, port)
|
||||
repo.insertAll(
|
||||
listOf(
|
||||
ev(1, Targets.KAFKA_MSG, "F1", """{"v":1}"""),
|
||||
ev(2, Targets.KAFKA_MSG, "F1", """{"v":2}"""),
|
||||
ev(3, Targets.KAFKA_MSG, "F1", """{"v":3}"""),
|
||||
),
|
||||
)
|
||||
|
||||
d.tick()
|
||||
|
||||
val f1Keys = port.sent.filter { it.topic == "msg" }.map { it.payload }
|
||||
assertEquals(listOf("""{"v":1}""", """{"v":2}""", """{"v":3}"""), f1Keys) // 同 FLID 不越队
|
||||
}
|
||||
}
|
||||
|
||||
/** KAFKA:msg 对指定 FLID 的发送永远失败,其他 FLID 照常记录,用来验证按 FLID 暂停。 */
|
||||
private class FailingMsgKeyPort(private val failingKey: String) : DeliveryPort {
|
||||
val sent = mutableListOf<StubDeliveryPort.Sent>()
|
||||
|
||||
override fun sendKafka(topic: String, key: String, payloadJson: String) {
|
||||
if (key == failingKey) throw IllegalStateException("broker-down:$key")
|
||||
sent.add(StubDeliveryPort.Sent(topic, key, payloadJson))
|
||||
}
|
||||
|
||||
override fun sendKafkaSchd(topic: String, key: String, payloadJson: String) = Unit
|
||||
override fun sendKafkaNull(topic: String, key: String) = Unit
|
||||
}
|
||||
|
||||
/** KAFKA_SCHD 发送永远失败的投递端口,用来验证退避重试和最终转死信的闭环。 */
|
||||
|
||||
Reference in New Issue
Block a user