github.com/matrixorigin/matrixone@v0.7.0/pkg/txn/client/operator.go (about)

     1  // Copyright 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package client
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"sync"
    21  
    22  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    23  	"github.com/matrixorigin/matrixone/pkg/common/runtime"
    24  	"github.com/matrixorigin/matrixone/pkg/pb/metadata"
    25  	"github.com/matrixorigin/matrixone/pkg/pb/timestamp"
    26  	"github.com/matrixorigin/matrixone/pkg/pb/txn"
    27  	"github.com/matrixorigin/matrixone/pkg/txn/rpc"
    28  	"github.com/matrixorigin/matrixone/pkg/txn/util"
    29  	"go.uber.org/zap"
    30  )
    31  
    32  var (
    33  	readTxnErrors = map[uint16]struct{}{
    34  		moerr.ErrTAERead:      {},
    35  		moerr.ErrRpcError:     {},
    36  		moerr.ErrWaitTxn:      {},
    37  		moerr.ErrTxnNotFound:  {},
    38  		moerr.ErrTxnNotActive: {},
    39  	}
    40  	writeTxnErrors = map[uint16]struct{}{
    41  		moerr.ErrTAEWrite:     {},
    42  		moerr.ErrRpcError:     {},
    43  		moerr.ErrTxnNotFound:  {},
    44  		moerr.ErrTxnNotActive: {},
    45  	}
    46  	commitTxnErrors = map[uint16]struct{}{
    47  		moerr.ErrTAECommit:    {},
    48  		moerr.ErrTAERollback:  {},
    49  		moerr.ErrTAEPrepare:   {},
    50  		moerr.ErrRpcError:     {},
    51  		moerr.ErrTxnNotFound:  {},
    52  		moerr.ErrTxnNotActive: {},
    53  	}
    54  	rollbackTxnErrors = map[uint16]struct{}{
    55  		moerr.ErrTAERollback:  {},
    56  		moerr.ErrRpcError:     {},
    57  		moerr.ErrTxnNotFound:  {},
    58  		moerr.ErrTxnNotActive: {},
    59  	}
    60  )
    61  
    62  // WithTxnReadyOnly setup readyonly flag
    63  func WithTxnReadyOnly() TxnOption {
    64  	return func(tc *txnOperator) {
    65  		tc.option.readyOnly = true
    66  	}
    67  }
    68  
    69  // WithTxnDisable1PCOpt disable 1pc optimisation on distributed transaction. By default, mo enables 1pc
    70  // optimization for distributed transactions. For write operations, if all partitions' prepares are
    71  // executed successfully, then the transaction is considered committed and returned directly to the
    72  // client. Partitions' prepared data are committed asynchronously.
    73  func WithTxnDisable1PCOpt() TxnOption {
    74  	return func(tc *txnOperator) {
    75  		tc.option.disable1PCOpt = true
    76  	}
    77  }
    78  
    79  // WithTxnCNCoordinator set cn txn coodinator
    80  func WithTxnCNCoordinator() TxnOption {
    81  	return func(tc *txnOperator) {
    82  		tc.option.coordinator = true
    83  	}
    84  }
    85  
    86  // WithTxnCacheWrite Set cache write requests, after each Write call, the request will not be sent
    87  // to the DN node immediately, but stored in the Coordinator's memory, and the Coordinator will
    88  // choose the right time to send the cached requests. The following scenarios trigger the sending
    89  // of requests to DN:
    90  //  1. Before read, because the Coordinator is not aware of the format and content of the written data,
    91  //     it is necessary to send the cached write requests to the corresponding DN node each time Read is
    92  //     called, used to implement "read your write".
    93  //  2. Before commit, obviously, the cached write requests needs to be sent to the corresponding DN node
    94  //     before commit.
    95  func WithTxnCacheWrite() TxnOption {
    96  	return func(tc *txnOperator) {
    97  		tc.option.enableCacheWrite = true
    98  		tc.mu.cachedWrites = make(map[uint64][]txn.TxnRequest)
    99  	}
   100  }
   101  
   102  // WithSnapshotTS use a spec snapshot timestamp to build TxnOperator.
   103  func WithSnapshotTS(ts timestamp.Timestamp) TxnOption {
   104  	return func(tc *txnOperator) {
   105  		tc.mu.txn.SnapshotTS = ts
   106  	}
   107  }
   108  
   109  type txnOperator struct {
   110  	rt     runtime.Runtime
   111  	sender rpc.TxnSender
   112  	txnID  []byte
   113  
   114  	option struct {
   115  		readyOnly        bool
   116  		enableCacheWrite bool
   117  		disable1PCOpt    bool
   118  		coordinator      bool
   119  	}
   120  
   121  	mu struct {
   122  		sync.RWMutex
   123  		closed       bool
   124  		txn          txn.TxnMeta
   125  		cachedWrites map[uint64][]txn.TxnRequest
   126  	}
   127  }
   128  
   129  func newTxnOperator(
   130  	rt runtime.Runtime,
   131  	sender rpc.TxnSender,
   132  	txnMeta txn.TxnMeta,
   133  	options ...TxnOption) *txnOperator {
   134  	tc := &txnOperator{rt: rt, sender: sender}
   135  	tc.mu.txn = txnMeta
   136  	tc.txnID = txnMeta.ID
   137  	for _, opt := range options {
   138  		opt(tc)
   139  	}
   140  	tc.adjust()
   141  	util.LogTxnCreated(tc.rt.Logger(), tc.mu.txn)
   142  	return tc
   143  }
   144  
   145  func newTxnOperatorWithSnapshot(
   146  	rt runtime.Runtime,
   147  	sender rpc.TxnSender,
   148  	snapshot []byte) (*txnOperator, error) {
   149  	v := &txn.CNTxnSnapshot{}
   150  	if err := v.Unmarshal(snapshot); err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	tc := &txnOperator{rt: rt, sender: sender}
   155  	tc.mu.txn = v.Txn
   156  	tc.txnID = v.Txn.ID
   157  	tc.option.disable1PCOpt = v.Disable1PCOpt
   158  	tc.option.enableCacheWrite = v.EnableCacheWrite
   159  	tc.option.readyOnly = v.ReadyOnly
   160  
   161  	tc.adjust()
   162  	util.LogTxnCreated(tc.rt.Logger(), tc.mu.txn)
   163  	return tc, nil
   164  }
   165  
   166  func (tc *txnOperator) adjust() {
   167  	if tc.sender == nil {
   168  		tc.rt.Logger().Fatal("missing txn sender")
   169  	}
   170  	if len(tc.mu.txn.ID) == 0 {
   171  		tc.rt.Logger().Fatal("missing txn id")
   172  	}
   173  	if tc.mu.txn.SnapshotTS.IsEmpty() {
   174  		tc.rt.Logger().Fatal("missing txn snapshot timestamp")
   175  	}
   176  	if tc.option.readyOnly && tc.option.enableCacheWrite {
   177  		tc.rt.Logger().Fatal("readyOnly and delayWrites cannot both be set")
   178  	}
   179  }
   180  
   181  func (tc *txnOperator) Txn() txn.TxnMeta {
   182  	return tc.getTxnMeta(false)
   183  }
   184  
   185  func (tc *txnOperator) Snapshot() ([]byte, error) {
   186  	tc.mu.Lock()
   187  	defer tc.mu.Unlock()
   188  
   189  	if err := tc.checkStatus(true); err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	snapshot := &txn.CNTxnSnapshot{
   194  		Txn:              tc.mu.txn,
   195  		ReadyOnly:        tc.option.readyOnly,
   196  		EnableCacheWrite: tc.option.enableCacheWrite,
   197  		Disable1PCOpt:    tc.option.disable1PCOpt,
   198  	}
   199  	return snapshot.Marshal()
   200  }
   201  
   202  func (tc *txnOperator) ApplySnapshot(data []byte) error {
   203  	if !tc.option.coordinator {
   204  		tc.rt.Logger().Fatal("apply snapshot on non-coordinator txn operator")
   205  	}
   206  
   207  	tc.mu.Lock()
   208  	defer tc.mu.Unlock()
   209  
   210  	if err := tc.checkStatus(true); err != nil {
   211  		return err
   212  	}
   213  
   214  	snapshot := &txn.CNTxnSnapshot{}
   215  	if err := snapshot.Unmarshal(data); err != nil {
   216  		return err
   217  	}
   218  
   219  	if !bytes.Equal(snapshot.Txn.ID, tc.mu.txn.ID) {
   220  		tc.rt.Logger().Fatal("apply snapshot with invalid txn id")
   221  	}
   222  
   223  	for _, dn := range snapshot.Txn.DNShards {
   224  		has := false
   225  		for _, v := range tc.mu.txn.DNShards {
   226  			if v.ShardID == dn.ShardID {
   227  				has = true
   228  				break
   229  			}
   230  		}
   231  
   232  		if !has {
   233  			tc.mu.txn.DNShards = append(tc.mu.txn.DNShards, dn)
   234  		}
   235  	}
   236  	util.LogTxnUpdated(tc.rt.Logger(), tc.mu.txn)
   237  	return nil
   238  }
   239  
   240  func (tc *txnOperator) Read(ctx context.Context, requests []txn.TxnRequest) (*rpc.SendResult, error) {
   241  	util.LogTxnRead(tc.rt.Logger(), tc.getTxnMeta(false))
   242  
   243  	for idx := range requests {
   244  		requests[idx].Method = txn.TxnMethod_Read
   245  	}
   246  
   247  	if err := tc.validate(ctx, false); err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	requests = tc.maybeInsertCachedWrites(ctx, requests, false)
   252  	return tc.trimResponses(tc.handleError(tc.doSend(ctx, requests, false)))
   253  }
   254  
   255  func (tc *txnOperator) Write(ctx context.Context, requests []txn.TxnRequest) (*rpc.SendResult, error) {
   256  	util.LogTxnWrite(tc.rt.Logger(), tc.getTxnMeta(false))
   257  
   258  	return tc.doWrite(ctx, requests, false)
   259  }
   260  
   261  func (tc *txnOperator) WriteAndCommit(ctx context.Context, requests []txn.TxnRequest) (*rpc.SendResult, error) {
   262  	return tc.doWrite(ctx, requests, true)
   263  }
   264  
   265  func (tc *txnOperator) Commit(ctx context.Context) error {
   266  	util.LogTxnCommit(tc.rt.Logger(), tc.getTxnMeta(false))
   267  
   268  	if tc.option.readyOnly {
   269  		return nil
   270  	}
   271  
   272  	result, err := tc.doWrite(ctx, nil, true)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	if result != nil {
   277  		result.Release()
   278  	}
   279  	return nil
   280  }
   281  
   282  func (tc *txnOperator) Rollback(ctx context.Context) error {
   283  	util.LogTxnRollback(tc.rt.Logger(), tc.getTxnMeta(false))
   284  
   285  	tc.mu.Lock()
   286  	defer func() {
   287  		tc.mu.closed = true
   288  		tc.mu.Unlock()
   289  	}()
   290  
   291  	if len(tc.mu.txn.DNShards) == 0 {
   292  		return nil
   293  	}
   294  
   295  	result, err := tc.handleError(tc.doSend(ctx, []txn.TxnRequest{{
   296  		Method:          txn.TxnMethod_Rollback,
   297  		RollbackRequest: &txn.TxnRollbackRequest{},
   298  	}}, true))
   299  	if err != nil {
   300  		if moerr.IsMoErrCode(err, moerr.ErrTxnClosed) {
   301  			return nil
   302  		}
   303  		return err
   304  	}
   305  	if result != nil {
   306  		result.Release()
   307  	}
   308  	return nil
   309  }
   310  
   311  func (tc *txnOperator) Debug(ctx context.Context, requests []txn.TxnRequest) (*rpc.SendResult, error) {
   312  	for idx := range requests {
   313  		requests[idx].Method = txn.TxnMethod_DEBUG
   314  	}
   315  
   316  	if err := tc.validate(ctx, false); err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	requests = tc.maybeInsertCachedWrites(ctx, requests, false)
   321  	return tc.trimResponses(tc.handleError(tc.doSend(ctx, requests, false)))
   322  }
   323  
   324  func (tc *txnOperator) doWrite(ctx context.Context, requests []txn.TxnRequest, commit bool) (*rpc.SendResult, error) {
   325  	for idx := range requests {
   326  		requests[idx].Method = txn.TxnMethod_Write
   327  	}
   328  
   329  	if tc.option.readyOnly {
   330  		tc.rt.Logger().Fatal("can not write on ready only transaction")
   331  	}
   332  
   333  	if commit {
   334  		tc.mu.Lock()
   335  		defer func() {
   336  			tc.mu.closed = true
   337  			tc.mu.Unlock()
   338  		}()
   339  	}
   340  
   341  	if err := tc.validate(ctx, commit); err != nil {
   342  		return nil, err
   343  	}
   344  
   345  	tc.updateWritePartitions(requests, commit)
   346  
   347  	// delayWrites enabled, no responses
   348  	if !commit && tc.maybeCacheWrites(requests, commit) {
   349  		return nil, nil
   350  	}
   351  
   352  	if commit {
   353  		if len(tc.mu.txn.DNShards) == 0 { // commit no write handled txn
   354  			return nil, nil
   355  		}
   356  		requests = tc.maybeInsertCachedWrites(ctx, requests, true)
   357  		requests = append(requests, txn.TxnRequest{
   358  			Method: txn.TxnMethod_Commit,
   359  			Flag:   txn.SkipResponseFlag,
   360  			CommitRequest: &txn.TxnCommitRequest{
   361  				Disable1PCOpt: tc.option.disable1PCOpt,
   362  			}})
   363  	}
   364  	return tc.trimResponses(tc.handleError(tc.doSend(ctx, requests, commit)))
   365  }
   366  
   367  func (tc *txnOperator) updateWritePartitions(requests []txn.TxnRequest, locked bool) {
   368  	if len(requests) == 0 {
   369  		return
   370  	}
   371  
   372  	if !locked {
   373  		tc.mu.Lock()
   374  		defer tc.mu.Unlock()
   375  	}
   376  
   377  	for _, req := range requests {
   378  		tc.addPartitionLocked(req.CNRequest.Target)
   379  	}
   380  }
   381  
   382  func (tc *txnOperator) addPartitionLocked(dn metadata.DNShard) {
   383  	for idx := range tc.mu.txn.DNShards {
   384  		if tc.mu.txn.DNShards[idx].ShardID == dn.ShardID {
   385  			return
   386  		}
   387  	}
   388  	tc.mu.txn.DNShards = append(tc.mu.txn.DNShards, dn)
   389  	util.LogTxnUpdated(tc.rt.Logger(), tc.mu.txn)
   390  }
   391  
   392  func (tc *txnOperator) validate(ctx context.Context, locked bool) error {
   393  	if _, ok := ctx.Deadline(); !ok {
   394  		tc.rt.Logger().Fatal("context deadline set")
   395  	}
   396  
   397  	return tc.checkStatus(locked)
   398  }
   399  
   400  func (tc *txnOperator) checkStatus(locked bool) error {
   401  	if !locked {
   402  		tc.mu.RLock()
   403  		defer tc.mu.RUnlock()
   404  	}
   405  
   406  	if tc.mu.closed {
   407  		return moerr.NewTxnClosedNoCtx(tc.txnID)
   408  	}
   409  	return nil
   410  }
   411  
   412  func (tc *txnOperator) maybeCacheWrites(requests []txn.TxnRequest, locked bool) bool {
   413  	if tc.option.enableCacheWrite {
   414  		tc.mu.Lock()
   415  		defer tc.mu.Unlock()
   416  		for idx := range requests {
   417  			requests[idx].Flag |= txn.SkipResponseFlag
   418  			dn := requests[idx].CNRequest.Target.ShardID
   419  			tc.mu.cachedWrites[dn] = append(tc.mu.cachedWrites[dn], requests[idx])
   420  		}
   421  		return true
   422  	}
   423  	return false
   424  }
   425  
   426  func (tc *txnOperator) maybeInsertCachedWrites(ctx context.Context, requests []txn.TxnRequest, locked bool) []txn.TxnRequest {
   427  	if len(requests) == 0 || !tc.option.enableCacheWrite {
   428  		return requests
   429  	}
   430  
   431  	if !locked {
   432  		tc.mu.Lock()
   433  		defer tc.mu.Unlock()
   434  	}
   435  
   436  	if len(tc.mu.cachedWrites) == 0 {
   437  		return requests
   438  	}
   439  
   440  	newRequests := requests
   441  	hasCachedWrites := false
   442  	insertCount := 0
   443  	for idx := range requests {
   444  		dn := requests[idx].CNRequest.Target.ShardID
   445  		if writes, ok := tc.getCachedWritesLocked(dn); ok {
   446  			if !hasCachedWrites {
   447  				// copy all requests into newRequests if cached writes encountered
   448  				newRequests = append([]txn.TxnRequest(nil), requests[:idx]...)
   449  			}
   450  			newRequests = append(newRequests, writes...)
   451  			tc.clearCachedWritesLocked(dn)
   452  			hasCachedWrites = true
   453  			insertCount += len(writes)
   454  		}
   455  		if hasCachedWrites {
   456  			newRequests = append(newRequests, requests[idx])
   457  		}
   458  	}
   459  	return newRequests
   460  }
   461  
   462  func (tc *txnOperator) getCachedWritesLocked(dn uint64) ([]txn.TxnRequest, bool) {
   463  	writes, ok := tc.mu.cachedWrites[dn]
   464  	if !ok || len(writes) == 0 {
   465  		return nil, false
   466  	}
   467  	return writes, true
   468  }
   469  
   470  func (tc *txnOperator) clearCachedWritesLocked(dn uint64) {
   471  	delete(tc.mu.cachedWrites, dn)
   472  }
   473  
   474  func (tc *txnOperator) getTxnMeta(locked bool) txn.TxnMeta {
   475  	if !locked {
   476  		tc.mu.RLock()
   477  		defer tc.mu.RUnlock()
   478  	}
   479  	return tc.mu.txn
   480  }
   481  
   482  func (tc *txnOperator) doSend(ctx context.Context, requests []txn.TxnRequest, locked bool) (*rpc.SendResult, error) {
   483  	txnMeta := tc.getTxnMeta(locked)
   484  	for idx := range requests {
   485  		requests[idx].Txn = txnMeta
   486  	}
   487  
   488  	util.LogTxnSendRequests(tc.rt.Logger(), requests)
   489  	result, err := tc.sender.Send(ctx, requests)
   490  	if err != nil {
   491  		util.LogTxnSendRequestsFailed(tc.rt.Logger(), requests, err)
   492  		return nil, err
   493  	}
   494  	util.LogTxnReceivedResponses(tc.rt.Logger(), result.Responses)
   495  	return result, nil
   496  }
   497  
   498  func (tc *txnOperator) handleError(result *rpc.SendResult, err error) (*rpc.SendResult, error) {
   499  	if err != nil {
   500  		return nil, err
   501  	}
   502  
   503  	for _, resp := range result.Responses {
   504  		if err := tc.handleErrorResponse(resp); err != nil {
   505  			result.Release()
   506  			return nil, err
   507  		}
   508  	}
   509  	return result, nil
   510  }
   511  
   512  func (tc *txnOperator) handleErrorResponse(resp txn.TxnResponse) error {
   513  	switch resp.Method {
   514  	case txn.TxnMethod_Read:
   515  		if err := tc.checkResponseTxnStatusForReadWrite(resp); err != nil {
   516  			return err
   517  		}
   518  		return tc.checkTxnError(resp.TxnError, readTxnErrors)
   519  	case txn.TxnMethod_Write:
   520  		if err := tc.checkResponseTxnStatusForReadWrite(resp); err != nil {
   521  			return err
   522  		}
   523  		return tc.checkTxnError(resp.TxnError, writeTxnErrors)
   524  	case txn.TxnMethod_Commit:
   525  		if err := tc.checkResponseTxnStatusForCommit(resp); err != nil {
   526  			return err
   527  		}
   528  		return tc.checkTxnError(resp.TxnError, commitTxnErrors)
   529  	case txn.TxnMethod_Rollback:
   530  		if err := tc.checkResponseTxnStatusForRollback(resp); err != nil {
   531  			return err
   532  		}
   533  		return tc.checkTxnError(resp.TxnError, rollbackTxnErrors)
   534  	case txn.TxnMethod_DEBUG:
   535  		if resp.TxnError != nil {
   536  			return resp.TxnError.UnwrapError()
   537  		}
   538  		return nil
   539  	default:
   540  		tc.rt.Logger().Fatal("invalid response",
   541  			zap.String("response", resp.DebugString()))
   542  	}
   543  	return nil
   544  }
   545  
   546  func (tc *txnOperator) checkResponseTxnStatusForReadWrite(resp txn.TxnResponse) error {
   547  	if resp.TxnError != nil {
   548  		return nil
   549  	}
   550  
   551  	txnMeta := resp.Txn
   552  	if txnMeta == nil {
   553  		return moerr.NewTxnClosedNoCtx(tc.txnID)
   554  	}
   555  
   556  	switch txnMeta.Status {
   557  	case txn.TxnStatus_Active:
   558  		return nil
   559  	case txn.TxnStatus_Aborted, txn.TxnStatus_Aborting,
   560  		txn.TxnStatus_Committed, txn.TxnStatus_Committing:
   561  		return moerr.NewTxnClosedNoCtx(tc.txnID)
   562  	default:
   563  		tc.rt.Logger().Fatal("invalid response status for read or write",
   564  			util.TxnField(*txnMeta))
   565  	}
   566  	return nil
   567  }
   568  
   569  func (tc *txnOperator) checkTxnError(txnError *txn.TxnError, possibleErrorMap map[uint16]struct{}) error {
   570  	if txnError == nil {
   571  		return nil
   572  	}
   573  
   574  	// use txn internal error code to check error
   575  	txnCode := uint16(txnError.TxnErrCode)
   576  	if txnCode == moerr.ErrDNShardNotFound {
   577  		// do we still have the uuid and shard id?
   578  		return moerr.NewDNShardNotFoundNoCtx("", 0xDEADBEAF)
   579  	}
   580  
   581  	if _, ok := possibleErrorMap[txnCode]; ok {
   582  		return txnError.UnwrapError()
   583  	}
   584  
   585  	panic(moerr.NewInternalErrorNoCtx("invalid txn error, code %d, msg %s", txnCode, txnError.DebugString()))
   586  }
   587  
   588  func (tc *txnOperator) checkResponseTxnStatusForCommit(resp txn.TxnResponse) error {
   589  	if resp.TxnError != nil {
   590  		return nil
   591  	}
   592  
   593  	txnMeta := resp.Txn
   594  	if txnMeta == nil {
   595  		return moerr.NewTxnClosedNoCtx(tc.txnID)
   596  	}
   597  
   598  	switch txnMeta.Status {
   599  	case txn.TxnStatus_Committed, txn.TxnStatus_Aborted:
   600  		return nil
   601  	default:
   602  		panic(moerr.NewInternalErrorNoCtx("invalid respose status for commit, %v", txnMeta.Status))
   603  	}
   604  }
   605  
   606  func (tc *txnOperator) checkResponseTxnStatusForRollback(resp txn.TxnResponse) error {
   607  	if resp.TxnError != nil {
   608  		return nil
   609  	}
   610  
   611  	txnMeta := resp.Txn
   612  	if txnMeta == nil {
   613  		return moerr.NewTxnClosedNoCtx(tc.txnID)
   614  	}
   615  
   616  	switch txnMeta.Status {
   617  	case txn.TxnStatus_Aborted:
   618  		return nil
   619  	default:
   620  		panic(moerr.NewInternalErrorNoCtx("invalud response status for rollback %v", txnMeta.Status))
   621  	}
   622  }
   623  
   624  func (tc *txnOperator) trimResponses(result *rpc.SendResult, err error) (*rpc.SendResult, error) {
   625  	if err != nil {
   626  		return nil, err
   627  	}
   628  
   629  	values := result.Responses[:0]
   630  	for _, resp := range result.Responses {
   631  		if !resp.HasFlag(txn.SkipResponseFlag) {
   632  			values = append(values, resp)
   633  		}
   634  	}
   635  	result.Responses = values
   636  	return result, nil
   637  }