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("全绿。"); } }