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]

Reply via email to