feat: expose transaction failure stages and conservative outcomes

This commit is contained in:
2026-09-26 20:29:45 +08:00
parent 8eddef9cc8
commit 7a177744ab
25 changed files with 1171 additions and 44 deletions

View File

@@ -0,0 +1,63 @@
# v0.25 事务错误验收(2026-09-26)
目标是让业务可靠地区分错误阶段和框架实际掌握的事务结果。保持 InTx 签名,保留原始/回滚错误链;不引入业务补偿、文件删除或事务重试。
## 回归
- `GO_OMNI_SCAFFOLD_INTEGRATION=1 go test -race ./...`:49 个有测试的包通过,包含既有生成消费者、响应目录、生命周期、上传下载、配置等回归。
- `GOTOOLCHAIN=go1.22.12 go test ./database ./errors ./app`:通过。
- `go vet ./...`、`go build ./...`:通过。
- `go test -race ./database -run '^TestInTxCancellation' -count=20`:通过,包含 20 × 128 次取消与回滚竞争检查。
- `go run ./examples/transactions`:六种结果通过,包括“已保存但提交报错”时不清理文件的演示决策;示例没有真实文件删除。
| 场景 | 验证结果 |
| --- | --- |
| 开启失败 | callback 不执行,StageBegin/NotStarted,原始驱动或 context 原因可 Is/As;nil DB、nil callback 在开启前报错 |
| 成功事务 | callback/Commit 各一次,返回 nil,不再调用显式 Rollback;context、隔离级别、只读选项传递保持 |
| 回调失败 | 显式回滚 nil 才返回 Work/RolledBack;原始业务原因可 Is/As;回滚报错为 Unknown 且两条原因完整保留 |
| 提交失败 | 同样的驱动错误分别模拟已保存/未保存,都返回 Commit/Unknown;超时、ErrBadConn 也不当成回滚;不重试 Commit/callback |
| 取消 | 开启前取消、回调取消、自动回滚完成、取消与显式回滚竞争、提交过程中取消;ErrTxDone 从不表示回滚成功 |
| 原因链 | 外层再次 `%w` 包装后 As/Is 可用;主要 typed cause 优先,回滚 typed cause 可单独找到;嵌套 TxError 不压平 |
| 异常与隔离 | panic 原值传播,Goexit 正常展开;都尝试一次回滚,不提交;64 个并发调用的原因/结果互不串扰 |
| HTTP 兼容 | 回调返回业务错误目录引用与参数,包装后仍输出项目 HTTP 200、数字业务码、提示与 data:null;回滚附加原因、提交内部原因不外泄 |
## 真实 MySQL
```sh
python3 scripts/verifyConnector.py --local --gorm --phases --report reports/transactionMySQL.json
```
使用独立本地 MySQL 8.4 容器,1 CPU / 768 MiB / 128 pids,TLS 与临时随机凭据;不连接接入项目数据库或远程服务器。53 条通过记录(含子用例),耗时 13.2 秒。容器、数据卷、证书与临时消费者已经清理。完整证据见 [transactionMySQL.json](transactionMySQL.json)。
- 正常 Commit 后,另一个连接看到记录;回调失败且真实 Rollback 成功,返回 Work/RolledBack,另一个连接确认没有该记录。
- 在真实驱动 Commit 成功返回后,由测试包装器注入“确认丢失”错误:另一个连接确认记录已经持久化,InTx 返回 Commit/Unknown,没有重试或误报回滚。
- 在真实驱动 Rollback 成功后注入确认错误:库只能返回 Work/Unknown,并保留业务原因与回滚确认错误;不能利用测试已知的数据库状态推断生产结果。
- 这是驱动返回边界的确定性故障注入,不等同于覆盖所有 TCP 断线时序。
- 真实生成项目的配置/TLS/独立连接池/关闭、可选 GORM 事务与连接池归属、后台 drain 后关闭数据库均通过。
- 本轮发现旧 MySQL 验收脚本仍依赖已移除的自动环境覆盖。现已改为测试配置文件显式引用变量,复测通过;没有恢复旧加载模式。
## 性能
Go 1.27.1、darwin/arm64、Apple M2 Max。使用相同演示驱动和 benchmark,比较 v0.24 的原 InTx 实现与当前实现,每场景三轮、300ms,取中位数。旧实现从提交 `8eddef9cc8e9902150f4f712a40bc761d4350415` 取出,临时 benchmark 文件测后删除。[原始结果](transactionBenchmark.txt)。
| 场景 | 原实现 ns/次 | 当前 ns/次 | 原分配 | 当前分配 |
| --- | ---: | ---: | ---: | ---: |
| 成功提交 | 617.3 | 587.0 | 392 B / 7 | 392 B / 7 |
| 开启失败 | 212.9 | 146.0 | 96 B / 3 | 80 B / 2 |
| 回调失败并成功回滚 | 556.5 | 540.9 | 280 B / 6 | 344 B / 7 |
| 回调和回滚同时失败 | 681.1 | 732.6 | 416 B / 10 | 344 B / 7 |
| 提交结果不确定 | 719.6 | 814.8 | 472 B / 9 | 456 B / 8 |
成功路径没有新增分配。原先直接返回的回调错误现在需要 TxError,增加 64 B / 1 次分配;其他失败路径移除了即时格式化/Join 的部分开销。部分失败路径耗时增加,以上是受 goroutine 调度影响的本机微基准,不含真实数据库网络、SQL 和磁盘耗时,不承诺固定提速比例或服务 QPS。
当前实现复现:
```sh
go test ./database -run '^$' -bench '^BenchmarkInTx$' -benchmem -benchtime=300ms -count=3
```
## 保证范围
Outcome 只属于当前受管理事务;回调不得自己提交/回滚、执行隐式提交 SQL、把事务交给其他 goroutine。其他连接、独立事务、非事务表、文件和第三方系统的副作用由业务负责。
标准库异步回滚不公开的驱动原因无法还原。只拿到 ErrTxDone 时保守返回 Unknown;直接观察到的回滚失败全部保留。panic/Goexit 保持原有传播,没有普通返回值可携带回滚原因。返回错误的文本/具体最外层类型有变化,调用方使用 errors.Is/As;GORM 自己的 Transaction 不属于本次接口。

View File

@@ -0,0 +1,36 @@
goos: darwin
goarch: arm64
pkg: git.yuntuo.hk/k2safe/go-omni/database
cpu: Apple M2 Max
BenchmarkLegacyInTx/success-12 623776 617.3 ns/op 392 B/op 7 allocs/op
BenchmarkLegacyInTx/success-12 715071 627.2 ns/op 392 B/op 7 allocs/op
BenchmarkLegacyInTx/success-12 686116 605.4 ns/op 392 B/op 7 allocs/op
BenchmarkLegacyInTx/beginFailure-12 1700702 213.6 ns/op 96 B/op 3 allocs/op
BenchmarkLegacyInTx/beginFailure-12 1692045 211.5 ns/op 96 B/op 3 allocs/op
BenchmarkLegacyInTx/beginFailure-12 1698073 212.9 ns/op 96 B/op 3 allocs/op
BenchmarkLegacyInTx/workFailure-12 787486 544.8 ns/op 280 B/op 6 allocs/op
BenchmarkLegacyInTx/workFailure-12 724378 557.3 ns/op 280 B/op 6 allocs/op
BenchmarkLegacyInTx/workFailure-12 780825 556.5 ns/op 280 B/op 6 allocs/op
BenchmarkLegacyInTx/rollbackFailure-12 598321 681.7 ns/op 416 B/op 10 allocs/op
BenchmarkLegacyInTx/rollbackFailure-12 555447 665.4 ns/op 416 B/op 10 allocs/op
BenchmarkLegacyInTx/rollbackFailure-12 516342 681.1 ns/op 416 B/op 10 allocs/op
BenchmarkLegacyInTx/commitUnknown-12 485095 734.8 ns/op 472 B/op 9 allocs/op
BenchmarkLegacyInTx/commitUnknown-12 549530 716.2 ns/op 472 B/op 9 allocs/op
BenchmarkLegacyInTx/commitUnknown-12 498938 719.6 ns/op 472 B/op 9 allocs/op
BenchmarkInTx/success-12 613156 597.3 ns/op 392 B/op 7 allocs/op
BenchmarkInTx/success-12 592444 584.3 ns/op 392 B/op 7 allocs/op
BenchmarkInTx/success-12 720130 587.0 ns/op 392 B/op 7 allocs/op
BenchmarkInTx/beginFailure-12 2452880 147.0 ns/op 80 B/op 2 allocs/op
BenchmarkInTx/beginFailure-12 2506814 146.0 ns/op 80 B/op 2 allocs/op
BenchmarkInTx/beginFailure-12 2485419 145.2 ns/op 80 B/op 2 allocs/op
BenchmarkInTx/workFailure-12 748887 538.1 ns/op 344 B/op 7 allocs/op
BenchmarkInTx/workFailure-12 753242 540.9 ns/op 344 B/op 7 allocs/op
BenchmarkInTx/workFailure-12 727633 592.8 ns/op 344 B/op 7 allocs/op
BenchmarkInTx/rollbackFailure-12 538176 732.6 ns/op 345 B/op 7 allocs/op
BenchmarkInTx/rollbackFailure-12 669040 740.1 ns/op 344 B/op 7 allocs/op
BenchmarkInTx/rollbackFailure-12 629571 696.0 ns/op 344 B/op 7 allocs/op
BenchmarkInTx/commitUnknown-12 650611 804.9 ns/op 456 B/op 8 allocs/op
BenchmarkInTx/commitUnknown-12 434694 1015 ns/op 456 B/op 8 allocs/op
BenchmarkInTx/commitUnknown-12 663369 814.8 ns/op 456 B/op 8 allocs/op
PASS
ok git.yuntuo.hk/k2safe/go-omni/database 14.818s

View File

@@ -0,0 +1,90 @@
{
"passed": true,
"frameworkVersion": "v0.25.0-preview",
"localSource": true,
"gormAdapter": true,
"phasedLifecycle": true,
"frameworkSum": null,
"freshModuleCache": false,
"noLocalReplace": false,
"mysqlImage": "sha256:1afd25fe334a1be64a3866495dd43a6bdba3c55dd1d6190ea12933163fe2843c",
"mysqlLimits": {
"cpus": 1,
"memoryMiB": 768,
"pids": 128
},
"durationSeconds": 13.2,
"tests": [
"TestConnectorIndependentPoolsAndContext",
"TestConnectorRejectsInvalidInputWithoutTakingOwnership",
"TestConnectorStartupFailuresReleaseResources/connect",
"TestConnectorStartupFailuresReleaseResources/ping",
"TestConnectorStartupFailuresReleaseResources",
"TestConnectorTimeoutBoundsConnectAndPing/connect",
"TestConnectorTimeoutBoundsConnectAndPing/ping",
"TestConnectorTimeoutBoundsConnectAndPing",
"TestConnectorHealthBoundsPoolWaitAndRecovers",
"TestConnectorIdleZeroAndLifetime/noIdle",
"TestConnectorIdleZeroAndLifetime/expired",
"TestConnectorIdleZeroAndLifetime",
"TestConnectorTransactionsKeepCommitAndRollbackSemantics",
"TestMySQLConnectorDriverLoggerIsolation",
"TestMySQLConnectorTLSAndPoolIsolation/untrustedCA",
"TestMySQLConnectorTLSAndPoolIsolation/wrongHostname",
"TestMySQLConnectorTLSAndPoolIsolation/commitAcknowledgementLost",
"TestMySQLConnectorTLSAndPoolIsolation/rollbackAcknowledgementLost",
"TestMySQLConnectorTLSAndPoolIsolation",
"TestHTTPFileAndEnvironment/server.json",
"TestHTTPFileAndEnvironment/server.yaml",
"TestHTTPFileAndEnvironment",
"TestHTTPInvalidAndUnsupportedSettings/READ_HEADER_TIMEOUTinvalid",
"TestHTTPInvalidAndUnsupportedSettings/READ_HEADER_TIMEOUT-1",
"TestHTTPInvalidAndUnsupportedSettings/READ_TIMEOUTinvalid",
"TestHTTPInvalidAndUnsupportedSettings/READ_TIMEOUT-1",
"TestHTTPInvalidAndUnsupportedSettings/WRITE_TIMEOUTinvalid",
"TestHTTPInvalidAndUnsupportedSettings/WRITE_TIMEOUT-1",
"TestHTTPInvalidAndUnsupportedSettings/IDLE_TIMEOUTinvalid",
"TestHTTPInvalidAndUnsupportedSettings/IDLE_TIMEOUT-1",
"TestHTTPInvalidAndUnsupportedSettings/MAX_HEADER_BYTESinvalid",
"TestHTTPInvalidAndUnsupportedSettings/MAX_HEADER_BYTES-1",
"TestHTTPInvalidAndUnsupportedSettings",
"TestHTTPConfiguredStandardServer",
"TestHTTPInvalidStartupReleasesResources",
"TestHTTPConfiguredNetworkLimits/nethttp/slowHeaders",
"TestHTTPConfiguredNetworkLimits/nethttp/slowBody",
"TestHTTPConfiguredNetworkLimits/nethttp/idle",
"TestHTTPConfiguredNetworkLimits/nethttp/writeBackpressure",
"TestGORMMySQLTransactionsCancellationAndLogs",
"TestStructuredMySQLAdapterTLS",
"TestHTTPConfiguredNetworkLimits/nethttp/headerLimit",
"TestHTTPConfiguredNetworkLimits/nethttp",
"TestHTTPConfiguredNetworkLimits/native/slowHeaders",
"TestHTTPConfiguredNetworkLimits/native/slowBody",
"TestHTTPConfiguredNetworkLimits/native/idle",
"TestHTTPConfiguredNetworkLimits/native/writeBackpressure",
"TestHTTPConfiguredNetworkLimits/native/headerLimit",
"TestHTTPConfiguredNetworkLimits/native",
"TestHTTPConfiguredNetworkLimits",
"TestGeneratedOptionalGORM",
"TestGeneratedMySQLConfigurationAndLifecycle",
"TestGeneratedMySQLBackgroundDrain"
],
"sourceHashes": {
"database/connector_test.go": "0663dfc9794ba24130225fa3b6418a6d5f75d57d0f01686151f70c7ea3f592c0",
"database/mysqlConnector_test.go": "db5a9f5a99fd46e8e84c4452663704172ddc26a57ad6af84d3165041ff5357e9",
"app/httpConfig_test.go": "b060dc0b76fb8e5feebae7e3b12cb7bca845693fcc097e2a0496a5ba937c837a",
"config/http_test.go": "6345fc06fb25f48f5cb26e464f525078408f9180da1c8135eda60969d9ca21d1",
"database/mysql/integration_test.go": "05b0a73d50363eef760e8a3324a7e9a68c598f7519c6360ad530503178b56f43",
"database/gormx/integration_test.go": "3a5ac66d5972efc9127c7f1cc473337f899a521b967d4ec1ce00a0dbe06188bb",
"scripts/verifyConnector.py": "2b1b1851a53efde49a44642bc92296b4b4f40bf84e0666389ec763a35f63b48a",
"scripts/testdata/mysqlGenerated_test.go": "419c83cf9d1c25e8791f92b697f8e74f55b0af76ac0bc41e1316ee87b6923711",
"database/transaction.go": "037f14ed495e6d024e13e2033c2e469b138dc5c1e1d6f6a9a73e723e094e71e1",
"scripts/testdata/phasedMySQL_test.go": "925ca9ce0036ce6d3833cc8be9e72f8a097c689b8a270051fa2b6471f2edb6bb",
"lifecycle/runtime.go": "7d890150b931cdc6c5d03f3706ea09b1d220a958d91fbd6ddc6715a9ec13a5d2",
"lifecycle/phases.go": "f76b9e046d725b5d7906e80a6f29dc247c5d453a64ac998c49d4fcffe0d84976",
"scripts/testdata/gormGenerated_test.go": "6fc476ec8c089c63b032c8cc707a83c88e685ce8599a59ab63006bad05cbf251",
"database/gormx/gorm.go": "512134d2c9aa7aa14b42071e837900ccf6b2fd8ed5550b686339c7b1605f14a2",
"database/gormx/pool.go": "227fac41c2a2ef64f2e9fc2b8a7202ba6fc22c61fd231e015fe08f5b1d97001d",
"database/gormx/logger.go": "e90a3d411e1b267d4abdf0ab795ef3d60ca4a443f7f9b1d51ccfe4cdae8aae4d"
}
}