package jnpf.audit.sdk;
import jnpf.audit.AuditApiConsts;
import jnpf.audit.AuditOperationIds;
import jnpf.audit.model.AuditEventDTO;
import org.slf4j.MDC;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* AuditTxHolder 的并发时序矩阵断言(M2 operationId 分叉修复,spec §6-A)。
*
*
本类模拟事务生命周期的编排顺序,逐条对照 spring-tx 6.2.12 的
* {@code AbstractPlatformTransactionManager} 字节码得出,改动前请先复核:
*
* 挂起 doSuspendSynchronization:
* getSynchronizations()(偏移 0)→ 逐个 suspend()(偏移 31)→ clearSynchronization()(偏移 39)
* 恢复 doResumeSynchronization:
* initSynchronization()(偏移 0)→ 循环内**逐元素交替** resume()(偏移 30)→ registerSynchronization()(偏移 36)
* ——注意是 resume 在前、register 在后,不是"先全部注册再全部恢复"
* 完成 triggerAfterCompletion:
* getSynchronizations()(偏移 7)→ clearSynchronization()(偏移 11)→ invokeAfterCompletion()(偏移 31)
* ——afterCompletion 回调在同步**被清除之后**执行,故本类用
* TransactionSynchronizationUtils.invokeAfterCompletion(list, status) 而非
* triggerAfterCompletion(int)(后者要求同步仍活跃,与真实相反)
*
*
* 本类是 A 轮保真度缺口的唯一书面依据:TransactionSynchronizationUtils 没有 suspend/resume
* 的公开触发器,挂起/恢复顺序全靠本类自行编排。真实链路交叉验证见 tasks/audit-opid-e2e.sh。
*
*
运行:bash tasks/audit-opid-checks.sh
*/
public class AuditTxHolderConcurrencyChecks {
private static final String KEY = AuditApiConsts.MDC_OPERATION_ID;
private static final List SENT = new ArrayList();
private static int failures = 0;
private static int checksRun = 0;
private static int dirtyTail = 0;
/** 假发送器:只记事件,不触碰 Feign / spool 两级降级链路(spec §6-A 已核实可行) */
private static final AuditEventPublisher PUB = new AuditEventPublisher(null, null) {
@Override
public void publishAsync(List events) {
SENT.addAll(events);
}
};
// ==================== 事务生命周期模拟(顺序依据见类注释)====================
private static void beginTx() {
TransactionSynchronizationManager.initSynchronization();
}
private static void commitTx() {
TransactionSynchronizationUtils.triggerBeforeCommit(false);
TransactionSynchronizationUtils.triggerBeforeCompletion();
TransactionSynchronizationUtils.triggerAfterCommit();
completeTx(TransactionSynchronization.STATUS_COMMITTED);
}
private static void rollbackTx() {
TransactionSynchronizationUtils.triggerBeforeCompletion();
completeTx(TransactionSynchronization.STATUS_ROLLED_BACK);
}
private static void completeTx(int status) {
List syncs = TransactionSynchronizationManager.getSynchronizations();
TransactionSynchronizationManager.clearSynchronization();
TransactionSynchronizationUtils.invokeAfterCompletion(syncs, status);
}
private static List suspendTx() {
List syncs = TransactionSynchronizationManager.getSynchronizations();
for (TransactionSynchronization s : syncs) {
s.suspend();
}
TransactionSynchronizationManager.clearSynchronization();
return syncs;
}
private static void resumeTx(List syncs) {
TransactionSynchronizationManager.initSynchronization();
for (TransactionSynchronization s : syncs) {
s.resume();
TransactionSynchronizationManager.registerSynchronization(s);
}
}
// ==================== 事件构造 ====================
/** 层 1 / 层 3 事件:不带 operationId,由 holder 决定(对应 Executor 插件与显式 record) */
private static void emitInner() {
AuditTxHolder.submit(dto(null), PUB);
}
/** 层 2 事件:切面显式带上 scope 的 id(照抄 AuditLogAspect.java:68,90) */
private static void emitOuter(String operationId) {
AuditTxHolder.submit(dto(operationId), PUB);
}
private static AuditEventDTO dto(String operationId) {
return AuditEventDTO.builder()
.operationId(operationId)
.eventType("BIZ_ACTION")
.actionCode("UPDATE")
.sourceLayer(1)
.build();
}
private static void emitSignUsed() {
AuditTxHolder.submit(AuditEventDTO.builder()
.eventType("E_SIGNATURE")
.actionCode("SIGN_USED")
.bizType("JIANCE_TASK")
.bizCode("LS-ROLLBACK")
.targetTable("lims_sign")
.targetId("sign-rollback")
.sourceLayer(3)
.build(), PUB);
}
// ==================== 断言工具 ====================
private static void reset() {
SENT.clear();
MDC.remove(KEY);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.clearSynchronization();
}
}
private static void check(String name, boolean ok, String detail) {
checksRun++;
if (ok) {
System.out.println(" [PASS] " + name);
} else {
failures++;
System.out.println(" [FAIL] " + name + " —— " + detail);
}
}
/** 每个场景收尾都要跑:MDC 必须归零、holder 资源必须已解绑(场景 9 的实现) */
private static void assertClean(String scenario) {
String mdc = MDC.get(KEY);
String res = AuditTxHolder.currentOperationId();
boolean ok = (mdc == null) && (res == null);
if (!ok) {
dirtyTail++;
}
check(scenario + " 末态干净(MDC 与 holder 资源均已清)", ok,
"MDC=" + mdc + ", currentOperationId=" + res);
}
private static Set idsOf(List events) {
Set ids = new HashSet();
for (AuditEventDTO e : events) {
ids.add(e.getOperationId());
}
return ids;
}
private static String dump() {
StringBuilder sb = new StringBuilder("发出 ").append(SENT.size()).append(" 条: ");
for (AuditEventDTO e : SENT) {
sb.append(e.getOperationId()).append(' ');
}
return sb.toString();
}
// ==================== 十一项场景 ====================
/** 场景 1:无事务进入、无内部事务提交(现状不回归) */
private static void s1() {
System.out.println("[S1] 无事务进入、无内部事务提交");
reset();
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
String scopeId = scope.operationId();
emitOuter(scopeId);
scope.close();
check("S1 层2 事件用 scope 的 id", SENT.size() == 1 && scopeId.equals(SENT.get(0).getOperationId()), dump());
assertClean("S1");
}
/** 场景 2:事务已激活进入 + 事务内提交(今日 lims 四处的形态,现状不回归) */
private static void s2() {
System.out.println("[S2] 事务已激活进入 + 事务内提交(今日 lims 形态)");
reset();
beginTx();
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
String scopeId = scope.operationId();
emitInner();
emitOuter(scopeId);
scope.close();
commitTx();
check("S2 层1 与层2 同 id", SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
check("S2 该 id 即 scope 的 id", idsOf(SENT).contains(scopeId), "scopeId=" + scopeId + ", " + dump());
assertClean("S2");
}
/** 场景 3:无事务进入 + 内部事务内提交(spec §1.2 分叉;全未改时必红) */
private static void s3() {
System.out.println("[S3] 无事务进入 + 内部事务内提交(§1.2 分叉)");
reset();
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
String scopeId = scope.operationId();
beginTx();
emitInner();
commitTx();
emitOuter(scopeId);
scope.close();
check("S3 层1 与层2 同 id", SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
assertClean("S3");
}
/** 场景 4:形态甲——作用域在事务外 + REQUIRES_NEW,buffer 由 submit 创建 */
private static void s4() {
System.out.println("[S4] 形态甲:作用域在事务外 + REQUIRES_NEW(buffer 由 submit 建)");
reset();
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
String scopeId = scope.operationId();
beginTx();
// 防假绿:本场景 buffer 必须由 submit 创建,且创建时 MDC 已有值
check("S4 前置:进入外层事务时 MDC 已被作用域写入", scopeId.equals(MDC.get(KEY)), "MDC=" + MDC.get(KEY));
check("S4 前置:外层事务此刻尚无 buffer", AuditTxHolder.currentOperationId() == null, "已有 buffer");
emitInner();
List susp = suspendTx();
beginTx();
emitInner();
commitTx();
resumeTx(susp);
emitInner();
commitTx();
emitOuter(scopeId);
scope.close();
check("S4 内外事务与层2 全部同 id", SENT.size() == 4 && idsOf(SENT).size() == 1, dump());
assertClean("S4");
}
/** 场景 5:形态乙——无作用域 + REQUIRES_NEW,内外必须各自独立(守 M1 终验#6) */
private static void s5() {
System.out.println("[S5] 形态乙:无作用域 + REQUIRES_NEW(内外必须不同 id)");
reset();
beginTx();
check("S5 前置:无作用域,MDC 为空", MDC.get(KEY) == null, "MDC=" + MDC.get(KEY));
emitInner();
List susp = suspendTx();
beginTx();
check("S5 前置:挂起后 MDC 必须被清空", MDC.get(KEY) == null, "MDC=" + MDC.get(KEY));
emitInner();
commitTx();
resumeTx(susp);
commitTx();
check("S5 内外事务 id 不同(终验#6 原行为)", SENT.size() == 2 && idsOf(SENT).size() == 2, dump());
assertClean("S5");
}
/**
* 场景 6:形态丙——事务已激活进入 + REQUIRES_NEW,buffer 由 beginOperation 创建。
* 这是 v1 设计漏掉的形态:只改 createBuffer 一行时**仍然红**,必须靠 scopeOwned 才转绿。
*/
private static void s6() {
System.out.println("[S6] 形态丙:事务已激活进入 + REQUIRES_NEW(buffer 由 beginOperation 建)");
reset();
beginTx();
// 防假绿(spec §6-A):必须是 MDC 为空时由 beginOperation 建 buffer,
// 否则会退化成场景 4 的 previousMdc 路径而假绿
check("S6 前置:beginOperation 之前 MDC 为空", MDC.get(KEY) == null, "MDC=" + MDC.get(KEY));
check("S6 前置:beginOperation 之前无 buffer", AuditTxHolder.currentOperationId() == null, "已有 buffer");
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
String scopeId = scope.operationId();
check("S6 前置:buffer 确由 beginOperation 创建", scopeId != null && scopeId.equals(AuditTxHolder.currentOperationId()),
"scopeId=" + scopeId + ", current=" + AuditTxHolder.currentOperationId());
List susp = suspendTx();
beginTx();
emitInner();
commitTx();
resumeTx(susp);
emitOuter(scopeId);
scope.close();
commitTx();
check("S6 内层 REQUIRES_NEW 与层2 同 id", SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
check("S6 该 id 即 scope 的 id", idsOf(SENT).contains(scopeId), "scopeId=" + scopeId + ", " + dump());
assertClean("S6");
}
/** 场景 7:形态丁——submit 先建 buffer、随后才进作用域 + REQUIRES_NEW(验"复用路径也置位") */
private static void s7() {
System.out.println("[S7] 形态丁:submit 先建 buffer、后进作用域 + REQUIRES_NEW");
reset();
beginTx();
emitInner();
String bufferId = AuditTxHolder.currentOperationId();
check("S7 前置:buffer 由 submit 创建", bufferId != null, "无 buffer");
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
String scopeId = scope.operationId();
check("S7 前置:作用域复用既有 buffer", bufferId.equals(scopeId), "bufferId=" + bufferId + ", scopeId=" + scopeId);
List susp = suspendTx();
beginTx();
emitInner();
commitTx();
resumeTx(susp);
emitOuter(scopeId);
scope.close();
commitTx();
check("S7 全链路同 id", SENT.size() == 3 && idsOf(SENT).size() == 1, dump());
assertClean("S7");
}
/** 场景 8:形态戊——标注方法自身 REQUIRES_NEW、调用方有事务 */
private static void s8() {
System.out.println("[S8] 形态戊:标注方法自身 REQUIRES_NEW、调用方有事务");
reset();
beginTx();
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
String scopeId = scope.operationId();
List susp = suspendTx();
beginTx();
emitInner();
commitTx();
resumeTx(susp);
emitOuter(scopeId);
scope.close();
commitTx();
check("S8 自身事务与调用方事务同 id", SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
check("S8 该 id 即 scope 的 id", idsOf(SENT).contains(scopeId), "scopeId=" + scopeId + ", " + dump());
assertClean("S8");
}
/** 场景 10:回滚不发(单事务,守 M1 原行为) */
private static void s10() {
System.out.println("[S10] 回滚不发(单事务)");
reset();
beginTx();
emitInner();
rollbackTx();
check("S10 回滚后无事件发出", SENT.isEmpty(), dump());
assertClean("S10");
}
/** 场景 11:形态丙下 内层回滚 + 外层提交;并含双回滚变体 */
private static void s11() {
System.out.println("[S11] 形态丙下 内层回滚 + 外层提交");
reset();
beginTx();
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
String scopeId = scope.operationId();
List susp = suspendTx();
beginTx();
emitInner();
rollbackTx();
resumeTx(susp);
emitOuter(scopeId);
scope.close();
commitTx();
check("S11 内层回滚不发、外层照发", SENT.size() == 1 && scopeId.equals(SENT.get(0).getOperationId()), dump());
assertClean("S11");
System.out.println("[S11b] 双回滚变体");
reset();
beginTx();
AuditTxHolder.OperationScope scope2 = AuditTxHolder.beginOperation(PUB);
List susp2 = suspendTx();
beginTx();
emitInner();
rollbackTx();
resumeTx(susp2);
emitOuter(scope2.operationId());
scope2.close();
rollbackTx();
check("S11b 内外双回滚:一条都不发", SENT.isEmpty(), dump());
assertClean("S11b");
}
/** SIGN_USED 与业务事件同事务:共享 operationId,整体回滚时均不发。 */
private static void s12() {
System.out.println("[S12] SIGN_USED 与业务事件的提交/回滚语义");
reset();
beginTx();
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB);
emitInner();
emitSignUsed();
scope.close();
commitTx();
check("S12 提交时业务事件与 SIGN_USED 共享 operationId",
SENT.size() == 2 && idsOf(SENT).size() == 1, dump());
assertClean("S12-commit");
reset();
beginTx();
AuditTxHolder.OperationScope rollbackScope = AuditTxHolder.beginOperation(PUB);
emitInner();
emitSignUsed();
rollbackScope.close();
rollbackTx();
check("S12 回滚时 SIGN_USED 与业务事件均不发", SENT.isEmpty(), dump());
assertClean("S12-rollback");
}
/** 同一 biz_sign 的业务与签名事件共享一个稳定 operationId。 */
private static void s13() {
System.out.println("[S13] biz_sign 关联作用域统一 operationId");
reset();
String signId = "839875756384423365";
beginTx();
AuditTxHolder.OperationScope scope = AuditTxHolder.beginOperation(PUB, AuditOperationIds.correlation(signId));
emitInner();
emitSignUsed();
scope.close();
commitTx();
check("S13 同一前端操作的业务与签名事件共享 operationId",
SENT.size() == 2 && idsOf(SENT).size() == 1
&& idsOf(SENT).contains(AuditOperationIds.correlation(signId)), dump());
assertClean("S13");
}
public static void main(String[] args) {
System.out.println("=== AuditTxHolder 并发时序矩阵(spec §6-A + SIGN_USED 回滚)===");
s1();
s2();
s3();
s4();
s5();
s6();
s7();
s8();
s10();
s11();
s12();
s13();
System.out.println();
System.out.println("[S9] 线程复用防线汇总:末态不干净的场景数 = " + dirtyTail);
System.out.println();
System.out.println("=== 断言 " + checksRun + " 条,失败 " + failures + " 条 ===");
if (failures > 0) {
System.out.println("红。改动前这是预期结果——对照 plan 的两层红基线表核对红的是哪几项。");
System.exit(1);
}
System.out.println("全绿。");
}
}