Ethan-Xingyue opened a new issue, #1156:
URL: https://github.com/apache/incubator-seata-go/issues/1156

   
   | Field | Value |
   | --- | --- |
   | Issue title | `[BUG] [RM][Getty/gRPC] Branch commit/rollback failures 
return locally without sending a failure response to the TC` |
   | Labels (exist in repo) | `bug`, `module/rm`, `remoting` |
   | Suggested priority | P1 |
   | Related | none known |
   | Verification | Reproduced 2026-09-02 on master `3bf73586` with go1.24.3 
darwin/arm64 (in-package test, `-gcflags=all=-l`) |
   
   ---
   
   ## 🚀 Go Version
   
   go1.24.3 darwin/arm64
   
   ## 📦 Seata-go Version
   
   master, commit 3bf73586af81db1bd428982c93d82000d80cb1c8 (fetched 2026-09-02)
   
   ## 💾 Operating System
   
   macOS
   
   ## 📝 Bug Description
   
   When the resource manager's `BranchCommit` / `BranchRollback` returns an 
error, all four handler paths (Getty commit, Getty rollback, gRPC commit, gRPC 
rollback) log the error and `return err` immediately. No response is sent to 
the TC. The response-building code that follows still contains an `if err != 
nil { resultCode = ResultCodeFailed ... }` branch, but it is unreachable. The 
Getty `OnMessage` and gRPC receive loops do not consume the error returned by 
`Process` either.
   
   As a result the TC never receives the retryable branch status; it only 
notices the failure through its own RPC timeout, which extends lock holding 
time and delays the retry.
   
   Locations at 3bf73586 (the early `return err` lines):
   
   - gRPC commit: 
https://github.com/apache/incubator-seata-go/blob/3bf73586af81db1bd428982c93d82000d80cb1c8/pkg/remoting/processor/client/rm_branch_commit_processor.go#L74-L78
   - Getty commit: 
https://github.com/apache/incubator-seata-go/blob/3bf73586af81db1bd428982c93d82000d80cb1c8/pkg/remoting/processor/client/rm_branch_commit_processor.go#L131-L135
   - gRPC rollback: 
https://github.com/apache/incubator-seata-go/blob/3bf73586af81db1bd428982c93d82000d80cb1c8/pkg/remoting/processor/client/rm_branch_rollback_processor.go#L76-L80
   - Getty rollback: 
https://github.com/apache/incubator-seata-go/blob/3bf73586af81db1bd428982c93d82000d80cb1c8/pkg/remoting/processor/client/rm_branch_rollback_processor.go#L130-L134
   
   ## 🔄 Steps to Reproduce
   
   1. Check out the commit:
   
   ```bash
   git clone https://github.com/apache/incubator-seata-go.git
   cd incubator-seata-go
   git checkout 3bf73586af81db1bd428982c93d82000d80cb1c8
   ```
   
   2. Save the following as 
`pkg/remoting/processor/client/audit_branch_response_repro_test.go`. It 
registers a resource manager whose commit/rollback always fail, patches 
`SendAsyncResponse` on both remoting clients to count calls, and drives the 
four handlers directly:
   
   ```go
   package client
   
   import (
        "context"
        "errors"
        "reflect"
        "sync"
        "testing"
   
        "github.com/agiledragon/gomonkey/v2"
   
        "seata.apache.org/seata-go/v2/pkg/protocol/branch"
        "seata.apache.org/seata-go/v2/pkg/protocol/message"
        "seata.apache.org/seata-go/v2/pkg/remoting/getty"
        grpcclient "seata.apache.org/seata-go/v2/pkg/remoting/grpc"
        "seata.apache.org/seata-go/v2/pkg/remoting/grpc/pb"
        "seata.apache.org/seata-go/v2/pkg/rm"
   )
   
   type auditFailingRM struct{ resources sync.Map }
   
   func (*auditFailingRM) BranchCommit(context.Context, rm.BranchResource) 
(branch.BranchStatus, error) {
        return branch.BranchStatusPhasetwoCommitFailedRetryable, 
errors.New("storage failed")
   }
   func (*auditFailingRM) BranchRollback(context.Context, rm.BranchResource) 
(branch.BranchStatus, error) {
        return branch.BranchStatusPhasetwoRollbackFailedRetryable, 
errors.New("storage failed")
   }
   func (*auditFailingRM) BranchRegister(context.Context, 
rm.BranchRegisterParam) (int64, error) {
        return 0, nil
   }
   func (*auditFailingRM) BranchReport(context.Context, rm.BranchReportParam) 
error { return nil }
   func (*auditFailingRM) LockQuery(context.Context, rm.LockQueryParam) (bool, 
error) { return false, nil }
   func (*auditFailingRM) RegisterResource(rm.Resource) error                   
    { return nil }
   func (*auditFailingRM) UnregisterResource(rm.Resource) error                 
    { return nil }
   func (f *auditFailingRM) GetCachedResources() *sync.Map                      
    { return &f.resources }
   func (*auditFailingRM) GetBranchType() branch.BranchType                     
    { return branch.BranchType(99) }
   
   func TestAuditBranchFailureStillRepliesToTCBothProtocols(t *testing.T) {
        rm.GetRmCacheInstance().RegisterResourceManager(&auditFailingRM{})
   
        gettySent, grpcSent := 0, 0
        gettyPatch := gomonkey.ApplyMethod(
                reflect.TypeOf(getty.GetGettyRemotingClient()), 
"SendAsyncResponse",
                func(*getty.GettyRemotingClient, int32, interface{}) error { 
gettySent++; return nil },
        )
        defer gettyPatch.Reset()
        grpcPatch := gomonkey.ApplyMethod(
                reflect.TypeOf(grpcclient.GetGrpcRemotingClient()), 
"SendAsyncResponse",
                func(*grpcclient.GrpcRemotingClient, int32, interface{}) error 
{ grpcSent++; return nil },
        )
        defer grpcPatch.Reset()
   
        bt := branch.BranchType(99)
        commit := &rmBranchCommitProcessor{}
        rollback := &rmBranchRollbackProcessor{}
        _ = commit.handleGettyBranchCommit(context.Background(), 
message.RpcMessage{
                ID: 1, Body: message.BranchCommitRequest{
                        AbstractBranchEndRequest: 
message.AbstractBranchEndRequest{BranchType: bt},
                },
        })
        _ = rollback.handleGettyBranchRollback(context.Background(), 
message.RpcMessage{
                ID: 2, Body: message.BranchRollbackRequest{
                        AbstractBranchEndRequest: 
message.AbstractBranchEndRequest{BranchType: bt},
                },
        })
        abstract := &pb.AbstractBranchEndRequestProto{BranchType: 
pb.BranchTypeProto(99)}
        _ = commit.handleGrpcBranchCommit(context.Background(), 
message.RpcMessage{
                ID: 3, Body: 
&pb.BranchCommitRequestProto{AbstractBranchEndRequest: abstract},
        })
        _ = rollback.handleGrpcBranchRollback(context.Background(), 
message.RpcMessage{
                ID: 4, Body: 
&pb.BranchRollbackRequestProto{AbstractBranchEndRequest: abstract},
        })
        if gettySent != 2 || grpcSent != 2 {
                t.Fatalf("failure responses: getty=%d grpc=%d, want 2 each", 
gettySent, grpcSent)
        }
   }
   ```
   
   3. Run (gomonkey needs inlining disabled):
   
   ```bash
   go test -gcflags=all=-l \
     -run '^TestAuditBranchFailureStillRepliesToTCBothProtocols$' \
     -count=1 -v ./pkg/remoting/processor/client
   ```
   
   Delete the temporary test file afterwards.
   
   ## ✅ Expected Behavior
   
   All four failures produce a response: two via Getty, two via gRPC. Each 
response carries the retryable branch status returned by the RM, 
`ResultCodeFailed`, the xid/branch id and the error message (`storage failed`). 
The TC does not have to wait for its RPC timeout.
   
   ## ❌ Actual Behavior
   
   ```text
   === RUN   TestAuditBranchFailureStillRepliesToTCBothProtocols
   ERROR: branch commit error: storage failed
   ERROR: branch rollback error: storage failed
   ERROR: branch commit error: storage failed
   ERROR: branch rollback error: storage failed
       audit_branch_response_repro_test.go:74: failure responses: getty=0 
grpc=0, want 2 each
   --- FAIL: TestAuditBranchFailureStillRepliesToTCBothProtocols (0.00s)
   FAIL
   FAIL seata.apache.org/seata-go/v2/pkg/remoting/processor/client      0.674s
   FAIL
   ```
   
   ## 💡 Possible Solution
   
   - Always build and send the response, for success and failure alike; on 
failure use the RM-provided retryable status, `ResultCodeFailed` and the error 
message.
   - If sending the response fails, keep the business error as well (for 
example `errors.Join(bizErr, sendErr)`), but never skip the send attempt.
   - Make the success/failure status mapping a table shared by Getty and gRPC 
so the transports only differ in encoding.
   
   Acceptance criteria:
   
   - [ ] The Getty/gRPC × commit/rollback test above passes.
   - [ ] Result code, branch status, xid, branch id and message of the failure 
response are asserted.
   - [ ] When `SendAsyncResponse` itself fails, both the business error and the 
send error are observable.
   - [ ] An end-to-end test against a real TC shows the TC receives the failed 
status immediately instead of timing out.


-- 
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