Ethan-Xingyue opened a new issue, #1176: URL: https://github.com/apache/incubator-seata-go/issues/1176
## 🚀 Go Version go1.24.3 darwin/arm64 ## 📦 Seata-go Version master, commit 3bf73586af81db1bd428982c93d82000d80cb1c8 (fetched 2026-09-02) ## 💾 Operating System macOS ## 📝 Bug Description `tm.Begin` records the transaction start as whole Unix seconds, typed as a `time.Duration`: - https://github.com/apache/incubator-seata-go/blob/3bf73586af81db1bd428982c93d82000d80cb1c8/pkg/tm/transaction_executor.go#L110 — `TimeInfo{createTime: time.Duration(time.Now().Unix()), timeout: gc.Timeout}` - https://github.com/apache/incubator-seata-go/blob/3bf73586af81db1bd428982c93d82000d80cb1c8/pkg/tm/context.go#L64-L67 — `createTime time.Duration` - https://github.com/apache/incubator-seata-go/blob/3bf73586af81db1bd428982c93d82000d80cb1c8/pkg/tm/global_transaction.go#L42-L47 — `IsTimeout` rebuilds the start as `time.Unix(int64(ti.createTime), 0)` The fraction of the current wall-clock second that had already elapsed before `Begin` is counted as transaction time. A transaction with a 500 ms timeout that begins 700 ms into a second is considered expired immediately. Any timeout below one second is non-deterministic, and second-level timeouts are off by up to one second. `IsTimeout` is consulted by both `Commit` implementations (`getty_global_transaction.go` L59, `grpc_global_transaction.go` L65), so a false timeout turns a successful business call into a rollback. Suggested priority: P1. ## 🔄 Steps to Reproduce 1. Check out the commit and create a throwaway module that points at the local checkout: ```bash git clone https://github.com/apache/incubator-seata-go.git cd incubator-seata-go git checkout 3bf73586af81db1bd428982c93d82000d80cb1c8 repo_root="$(pwd)" repro_dir="$(mktemp -d)" cd "$repro_dir" go mod init seata-repro go mod edit -require=seata.apache.org/seata-go/[email protected] go mod edit -replace=seata.apache.org/seata-go/v2="$repo_root" ``` 2. Save the following as `repro_test.go` in `$repro_dir`. It waits until the wall clock is at least 700 ms into a second, begins a 500 ms `Supports` transaction (no TC round trip needed) and checks `IsTimeout` right away: ```go package repro import ( "context" "testing" "time" "seata.apache.org/seata-go/v2/pkg/tm" ) func TestSubsecondTimeoutIsNotImmediatelyExpired(t *testing.T) { // Wait until we are at least 700ms into the current wall-clock second. for time.Now().Nanosecond() < 700_000_000 { time.Sleep(5 * time.Millisecond) } ctx := tm.InitSeataContext(context.Background()) if err := tm.Begin(ctx, &tm.GtxConfig{ Name: "subsecond", Propagation: tm.Supports, Timeout: 500 * time.Millisecond, }); err != nil { t.Fatal(err) } if tm.IsTimeout(ctx) { t.Fatal("500ms transaction is expired immediately") } } ``` 3. Run: ```bash go mod tidy go test -run '^TestSubsecondTimeoutIsNotImmediatelyExpired$' -count=1 -v ``` ## ✅ Expected Behavior `IsTimeout` is `false` immediately after `Begin` and becomes `true` only after 500 ms of real elapsed time. ## ❌ Actual Behavior ```text === RUN TestSubsecondTimeoutIsNotImmediatelyExpired repro_test.go:23: 500ms transaction is expired immediately --- FAIL: TestSubsecondTimeoutIsNotImmediatelyExpired (0.00s) FAIL FAIL seata-audit-repros/tx003 0.579s FAIL ``` (The package name in the last line comes from the multi-package scratch module the test was run in; with the steps above it reads `seata-repro`.) ## 💡 Possible Solution - Store a `time.Time` in `TimeInfo` so `time.Since` uses the monotonic clock (or at least `UnixNano`); do not use `time.Duration` to represent a point in time. - Inject a clock in tests so the regression test does not depend on the wall clock. - Define the semantics of `timeout <= 0`, very large durations and the default value. Acceptance criteria: - [ ] The test above passes and the production regression test does not need to wait for a specific wall-clock fraction. - [ ] Fake-clock cases for 1 ms, 500 ms, 1 s, default, zero and wall-clock jumps. - [ ] Getty and gRPC `Commit` decide identically for the same elapsed time. - [ ] Existing tests of the transaction end-phase flows still pass after changing `IsTimeout` / `TimeInfo`. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
