github.com/matrixorigin/matrixone@v1.2.0/pkg/txn/service/service_cn_handler.go (about)

     1  // Copyright 2021 - 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 service
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"math"
    21  	"time"
    22  
    23  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    24  	"github.com/matrixorigin/matrixone/pkg/common/runtime"
    25  	"github.com/matrixorigin/matrixone/pkg/pb/timestamp"
    26  	"github.com/matrixorigin/matrixone/pkg/pb/txn"
    27  	"github.com/matrixorigin/matrixone/pkg/txn/util"
    28  	v2 "github.com/matrixorigin/matrixone/pkg/util/metric/v2"
    29  	"go.uber.org/zap"
    30  )
    31  
    32  var (
    33  	rollbackIgnoreErrorCodes = map[uint16]struct{}{
    34  		moerr.ErrTxnNotFound: {},
    35  	}
    36  
    37  	prepareIgnoreErrorCodes = map[uint16]struct{}{
    38  		moerr.ErrTxnNotFound: {},
    39  	}
    40  )
    41  
    42  func (s *service) Read(ctx context.Context, request *txn.TxnRequest, response *txn.TxnResponse) error {
    43  	s.waitRecoveryCompleted()
    44  
    45  	util.LogTxnHandleRequest(request)
    46  	defer util.LogTxnHandleResult(response)
    47  
    48  	response.CNOpResponse = &txn.CNOpResponse{}
    49  	s.checkCNRequest(request)
    50  	if !s.validTNShard(request.GetTargetTN()) {
    51  		response.TxnError = txn.WrapError(moerr.NewTNShardNotFound(ctx, "", request.GetTargetTN().ShardID), 0)
    52  		return nil
    53  	}
    54  
    55  	s.waitClockTo(request.Txn.SnapshotTS)
    56  
    57  	// We do not write transaction information to sync.Map during read operations because commit and abort
    58  	// for read-only transactions are not sent to the TN node, so there is no way to clean up the transaction
    59  	// information in sync.Map.
    60  	result, err := s.storage.Read(ctx, request.Txn, request.CNRequest.OpCode, request.CNRequest.Payload)
    61  	if err != nil {
    62  		util.LogTxnReadFailed(request.Txn, err)
    63  		response.TxnError = txn.WrapError(err, moerr.ErrTAERead)
    64  		return nil
    65  	}
    66  	defer result.Release()
    67  
    68  	if len(result.WaitTxns()) > 0 {
    69  		util.LogTxnReadBlockedByUncommittedTxns(request.Txn, result.WaitTxns())
    70  		waiters := make([]*waiter, 0, len(result.WaitTxns()))
    71  		for _, txnID := range result.WaitTxns() {
    72  			txnCtx := s.getTxnContext(txnID)
    73  			// The transaction can not be found, it means the concurrent transaction to be waited for has already
    74  			// been committed or aborted.
    75  			if txnCtx == nil {
    76  				continue
    77  			}
    78  
    79  			w := acquireWaiter()
    80  			// txn has been committed or aborted between call s.getTxnContext and txnCtx.addWaiter
    81  			if !txnCtx.addWaiter(txnID, w, txn.TxnStatus_Committed) {
    82  				w.close()
    83  				continue
    84  			}
    85  
    86  			waiters = append(waiters, w)
    87  		}
    88  
    89  		for _, w := range waiters {
    90  			if err != nil {
    91  				w.close()
    92  				continue
    93  			}
    94  
    95  			// If no error occurs, then it must have waited until the final state of the transaction, not caring
    96  			// whether the final state is committed or aborted.
    97  			_, err = w.wait(ctx)
    98  			w.close()
    99  		}
   100  
   101  		if err != nil {
   102  			util.LogTxnWaitUncommittedTxnsFailed(request.Txn, result.WaitTxns(), err)
   103  			response.TxnError = txn.WrapError(err, moerr.ErrWaitTxn)
   104  			return nil
   105  		}
   106  	}
   107  
   108  	data, err := result.Read()
   109  	if err != nil {
   110  		util.LogTxnReadFailed(request.Txn, err)
   111  		response.TxnError = txn.WrapError(err, moerr.ErrTAERead)
   112  		return nil
   113  	}
   114  
   115  	response.CNOpResponse.Payload = data
   116  	txnMeta := request.Txn
   117  	response.Txn = &txnMeta
   118  	return nil
   119  }
   120  
   121  func (s *service) Write(ctx context.Context, request *txn.TxnRequest, response *txn.TxnResponse) error {
   122  	s.waitRecoveryCompleted()
   123  
   124  	util.LogTxnHandleRequest(request)
   125  	defer util.LogTxnHandleResult(response)
   126  
   127  	response.CNOpResponse = &txn.CNOpResponse{}
   128  	s.checkCNRequest(request)
   129  	if !s.validTNShard(request.GetTargetTN()) {
   130  		response.TxnError = txn.WrapError(moerr.NewTNShardNotFound(ctx, "", request.GetTargetTN().ShardID), 0)
   131  		return nil
   132  	}
   133  
   134  	txnID := request.Txn.ID
   135  	txnCtx, _ := s.maybeAddTxn(request.Txn)
   136  
   137  	// only commit and rollback can held write Lock
   138  	if !txnCtx.mu.TryRLock() {
   139  		util.LogTxnNotFoundOn(request.Txn, s.shard)
   140  		response.TxnError = txn.WrapError(moerr.NewTNShardNotFound(ctx, "", request.GetTargetTN().ShardID), 0)
   141  		return nil
   142  	}
   143  	defer txnCtx.mu.RUnlock()
   144  
   145  	newTxn := txnCtx.getTxnLocked()
   146  	if !bytes.Equal(newTxn.ID, txnID) {
   147  		util.LogTxnNotFoundOn(request.Txn, s.shard)
   148  		response.TxnError = txn.WrapError(moerr.NewTxnNotFound(ctx), 0)
   149  		return nil
   150  	}
   151  
   152  	response.Txn = &newTxn
   153  	if newTxn.Status != txn.TxnStatus_Active {
   154  		util.LogTxnWriteOnInvalidStatus(newTxn)
   155  		response.TxnError = txn.WrapError(moerr.NewTxnNotActive(ctx, ""), 0)
   156  		return nil
   157  	}
   158  
   159  	data, err := s.storage.Write(ctx, request.Txn, request.CNRequest.OpCode, request.CNRequest.Payload)
   160  	if err != nil {
   161  		util.LogTxnWriteFailed(newTxn, err)
   162  		response.TxnError = txn.WrapError(err, moerr.ErrTAEWrite)
   163  		return nil
   164  	}
   165  
   166  	response.CNOpResponse.Payload = data
   167  	return nil
   168  }
   169  
   170  func (s *service) Commit(ctx context.Context, request *txn.TxnRequest, response *txn.TxnResponse) error {
   171  	v2.TxnTNReceiveCommitCounter.Inc()
   172  	start := time.Now()
   173  	defer func() {
   174  		v2.TxnTNCommitDurationHistogram.Observe(time.Since(start).Seconds())
   175  	}()
   176  
   177  	s.waitRecoveryCompleted()
   178  
   179  	st := time.Now()
   180  	defer func() {
   181  		cost := time.Since(st)
   182  		if cost > time.Second {
   183  			util.GetLogger().Warn("commit txn too slow",
   184  				zap.Duration("cost", cost))
   185  		}
   186  	}()
   187  
   188  	util.LogTxnHandleRequest(request)
   189  	defer util.LogTxnHandleResult(response)
   190  
   191  	response.CommitResponse = &txn.TxnCommitResponse{}
   192  	if !s.validTNShard(request.GetTargetTN()) {
   193  		response.TxnError = txn.WrapError(moerr.NewTNShardNotFound(ctx, "", request.GetTargetTN().ShardID), 0)
   194  		return nil
   195  	}
   196  
   197  	if len(request.Txn.TNShards) == 0 {
   198  		s.logger.Fatal("commit with empty tn shards")
   199  	}
   200  
   201  	if len(request.Txn.LockTables) > 0 {
   202  		invalidBinds, err := s.allocator.Valid(
   203  			request.Txn.LockService,
   204  			request.Txn.ID,
   205  			request.Txn.LockTables,
   206  		)
   207  		if err != nil {
   208  			response.TxnError = txn.WrapError(err, 0)
   209  			return nil
   210  		}
   211  		if len(invalidBinds) > 0 {
   212  			response.CommitResponse.InvalidLockTables = invalidBinds
   213  			response.TxnError = txn.WrapError(moerr.NewLockTableBindChanged(ctx), 0)
   214  			return nil
   215  		}
   216  	}
   217  
   218  	txnID := request.Txn.ID
   219  	txnCtx := s.getTxnContext(txnID)
   220  	if txnCtx == nil {
   221  		util.LogTxnNotFoundOn(request.Txn, s.shard)
   222  		response.TxnError = txn.WrapError(moerr.NewTNShardNotFound(ctx, "", request.GetTargetTN().ShardID), 0)
   223  		return nil
   224  	}
   225  
   226  	// block all other concurrent read and write operations.
   227  	txnCtx.mu.Lock()
   228  	defer txnCtx.mu.Unlock()
   229  
   230  	newTxn := txnCtx.getTxnLocked()
   231  	if !bytes.Equal(newTxn.ID, txnID) {
   232  		util.LogTxnNotFoundOn(request.Txn, s.shard)
   233  		response.TxnError = txn.WrapError(moerr.NewTNShardNotFound(ctx, "", request.GetTargetTN().ShardID), 0)
   234  		return nil
   235  	}
   236  
   237  	cleanTxnContext := true
   238  	defer func() {
   239  		// remove txnCtx, commit can only execute once.
   240  		s.removeTxn(txnID)
   241  		if cleanTxnContext {
   242  			s.releaseTxnContext(txnCtx)
   243  		}
   244  	}()
   245  
   246  	response.Txn = &newTxn
   247  	if newTxn.Status != txn.TxnStatus_Active {
   248  		util.LogTxnCommitOnInvalidStatus(newTxn)
   249  		response.TxnError = txn.WrapError(moerr.NewTxnNotActive(ctx, ""), 0)
   250  		return nil
   251  	}
   252  
   253  	newTxn.TNShards = request.Txn.TNShards
   254  	changeStatus := func(status txn.TxnStatus) {
   255  		newTxn.Status = status
   256  		txnCtx.changeStatusLocked(status)
   257  	}
   258  
   259  	// fast path: write in only one DNShard.
   260  	if len(newTxn.TNShards) == 1 {
   261  		util.LogTxnStart1PCCommit(newTxn)
   262  
   263  		commitTS, err := s.storage.Commit(ctx, newTxn)
   264  		v2.TxnTNCommitHandledCounter.Inc()
   265  		if err != nil {
   266  			util.LogTxnStart1PCCommitFailed(newTxn, err)
   267  			response.TxnError = txn.WrapError(err, moerr.ErrTAECommit)
   268  			changeStatus(txn.TxnStatus_Aborted)
   269  		} else {
   270  			newTxn.CommitTS = commitTS
   271  			txnCtx.updateTxnLocked(newTxn)
   272  
   273  			changeStatus(txn.TxnStatus_Committed)
   274  			util.LogTxn1PCCommitCompleted(newTxn)
   275  		}
   276  		return nil
   277  	}
   278  
   279  	util.LogTxnStart2PCCommit(newTxn)
   280  
   281  	// slow path. 2pc transaction.
   282  	// 1. send prepare request to all DNShards.
   283  	// 2. start async commit task if all prepare succeed.
   284  	// 3. response to client txn committed.
   285  	for _, tn := range newTxn.TNShards {
   286  		txnCtx.mu.requests = append(txnCtx.mu.requests, txn.TxnRequest{
   287  			Txn:            newTxn,
   288  			Method:         txn.TxnMethod_Prepare,
   289  			PrepareRequest: &txn.TxnPrepareRequest{TNShard: tn},
   290  		})
   291  	}
   292  
   293  	// unlock and lock here, because the prepare request will be sent to the current TxnService, it
   294  	// will need to get the Lock when processing the Prepare.
   295  	txnCtx.mu.Unlock()
   296  	// FIXME: txnCtx.mu.requests without lock, is it safe?
   297  	util.LogTxnSendRequests(txnCtx.mu.requests)
   298  	result, err := s.sender.Send(ctx, txnCtx.mu.requests)
   299  	txnCtx.mu.Lock()
   300  	if err != nil {
   301  		util.LogTxnParallelPrepareFailed(newTxn, err)
   302  
   303  		changeStatus(txn.TxnStatus_Aborted)
   304  		response.TxnError = txn.WrapError(moerr.NewRpcError(ctx, err.Error()), 0)
   305  		s.startAsyncRollbackTask(newTxn)
   306  		return nil
   307  	}
   308  
   309  	defer result.Release()
   310  
   311  	// get latest txn metadata
   312  	newTxn = txnCtx.getTxnLocked()
   313  	newTxn.CommitTS = newTxn.PreparedTS
   314  
   315  	hasError := false
   316  	var txnErr *txn.TxnError
   317  	for idx, resp := range result.Responses {
   318  		if resp.TxnError != nil {
   319  			txnErr = resp.TxnError
   320  			hasError = true
   321  			util.LogTxnPrepareFailedOn(newTxn, newTxn.TNShards[idx], txnErr)
   322  			continue
   323  		}
   324  
   325  		if resp.Txn.PreparedTS.IsEmpty() {
   326  			s.logger.Fatal("missing prepared timestamp",
   327  				zap.String("target-dn-shard", newTxn.TNShards[idx].DebugString()),
   328  				util.TxnIDFieldWithID(newTxn.ID))
   329  		}
   330  
   331  		util.LogTxnPrepareCompletedOn(newTxn, newTxn.TNShards[idx], resp.Txn.PreparedTS)
   332  		if newTxn.CommitTS.Less(resp.Txn.PreparedTS) {
   333  			newTxn.CommitTS = resp.Txn.PreparedTS
   334  		}
   335  	}
   336  	if hasError {
   337  		changeStatus(txn.TxnStatus_Aborted)
   338  		response.TxnError = txnErr
   339  		s.startAsyncRollbackTask(newTxn)
   340  		return nil
   341  	}
   342  
   343  	util.LogTxnParallelPrepareCompleted(newTxn)
   344  
   345  	// All DNShards prepared means the transaction is committed
   346  	cleanTxnContext = false
   347  	txnCtx.updateTxnLocked(newTxn)
   348  	return s.startAsyncCommitTask(txnCtx)
   349  }
   350  
   351  func (s *service) Rollback(ctx context.Context, request *txn.TxnRequest, response *txn.TxnResponse) error {
   352  	s.waitRecoveryCompleted()
   353  
   354  	util.LogTxnHandleRequest(request)
   355  	defer util.LogTxnHandleResult(response)
   356  
   357  	response.RollbackResponse = &txn.TxnRollbackResponse{}
   358  	if !s.validTNShard(request.GetTargetTN()) {
   359  		response.TxnError = txn.WrapError(moerr.NewTNShardNotFound(ctx, "", request.GetTargetTN().ShardID), 0)
   360  		return nil
   361  	}
   362  
   363  	if len(request.Txn.TNShards) == 0 {
   364  		s.logger.Fatal("rollback with empty tn shards")
   365  	}
   366  
   367  	txnID := request.Txn.ID
   368  	txnCtx := s.getTxnContext(txnID)
   369  	if txnCtx == nil {
   370  		util.LogTxnNotFoundOn(request.Txn, s.shard)
   371  		response.TxnError = txn.WrapError(moerr.NewTxnNotFound(ctx), 0)
   372  		return nil
   373  	}
   374  
   375  	txnCtx.mu.Lock()
   376  	defer txnCtx.mu.Unlock()
   377  
   378  	newTxn := txnCtx.getTxnLocked()
   379  	if !bytes.Equal(newTxn.ID, txnID) {
   380  		util.LogTxnNotFoundOn(request.Txn, s.shard)
   381  		response.TxnError = txn.WrapError(moerr.NewTxnNotFound(ctx), 0)
   382  		return nil
   383  	}
   384  
   385  	response.Txn = &newTxn
   386  	newTxn.TNShards = request.Txn.TNShards
   387  	s.startAsyncRollbackTask(newTxn)
   388  
   389  	response.Txn.Status = txn.TxnStatus_Aborted
   390  	return nil
   391  }
   392  
   393  func (s *service) startAsyncRollbackTask(txnMeta txn.TxnMeta) {
   394  	err := s.stopper.RunTask(func(ctx context.Context) {
   395  		util.LogTxnStartAsyncRollback(txnMeta)
   396  
   397  		requests := make([]txn.TxnRequest, 0, len(txnMeta.TNShards))
   398  		for _, tn := range txnMeta.TNShards {
   399  			requests = append(requests, txn.TxnRequest{
   400  				Txn:                    txnMeta,
   401  				Method:                 txn.TxnMethod_RollbackTNShard,
   402  				RollbackTNShardRequest: &txn.TxnRollbackTNShardRequest{TNShard: tn},
   403  			})
   404  		}
   405  
   406  		s.parallelSendWithRetry(ctx, requests, rollbackIgnoreErrorCodes)
   407  		util.LogTxnRollbackCompleted(txnMeta)
   408  	})
   409  	if err != nil {
   410  		s.logger.Error("start rollback task failed",
   411  			zap.Error(err),
   412  			util.TxnIDFieldWithID(txnMeta.ID))
   413  	}
   414  }
   415  
   416  func (s *service) Debug(ctx context.Context, request *txn.TxnRequest, response *txn.TxnResponse) error {
   417  	data, err := s.storage.Debug(ctx, request.Txn, request.CNRequest.OpCode, request.CNRequest.Payload)
   418  	if err != nil {
   419  		response.TxnError = txn.WrapError(err, moerr.ErrTAEDebug)
   420  		return nil
   421  	}
   422  	response.CNOpResponse = &txn.CNOpResponse{
   423  		Payload: data,
   424  	}
   425  	return nil
   426  }
   427  
   428  func (s *service) startAsyncCommitTask(txnCtx *txnContext) error {
   429  	return s.stopper.RunTask(func(ctx context.Context) {
   430  		txnCtx.mu.Lock()
   431  		defer txnCtx.mu.Unlock()
   432  
   433  		txnMeta := txnCtx.getTxnLocked()
   434  		util.LogTxnStartAsyncCommit(txnMeta)
   435  
   436  		if txnMeta.Status != txn.TxnStatus_Committing {
   437  			for {
   438  				err := s.storage.Committing(ctx, txnMeta)
   439  				if err == nil {
   440  					txnCtx.changeStatusLocked(txn.TxnStatus_Committing)
   441  					break
   442  				}
   443  				util.LogTxnCommittingFailed(txnMeta, err)
   444  				// TODO: make config
   445  				time.Sleep(time.Second)
   446  			}
   447  		}
   448  
   449  		util.LogTxnCommittingCompleted(txnMeta)
   450  
   451  		requests := make([]txn.TxnRequest, 0, len(txnMeta.TNShards)-1)
   452  		for _, tn := range txnMeta.TNShards[1:] {
   453  			requests = append(requests, txn.TxnRequest{
   454  				Txn:                  txnMeta,
   455  				Method:               txn.TxnMethod_CommitTNShard,
   456  				CommitTNShardRequest: &txn.TxnCommitTNShardRequest{TNShard: tn},
   457  			})
   458  		}
   459  
   460  		// no timeout, keep retry until TxnService.Close
   461  		ctx, cancel := context.WithTimeout(ctx, time.Duration(math.MaxInt64))
   462  		defer cancel()
   463  
   464  		if result := s.parallelSendWithRetry(ctx, requests, rollbackIgnoreErrorCodes); result != nil {
   465  			result.Release()
   466  			if s.logger.Enabled(zap.DebugLevel) {
   467  				s.logger.Debug("other dnshards committed",
   468  					util.TxnIDFieldWithID(txnMeta.ID))
   469  			}
   470  
   471  			if _, err := s.storage.Commit(ctx, txnMeta); err != nil {
   472  				s.logger.Fatal("commit failed after prepared",
   473  					util.TxnIDFieldWithID(txnMeta.ID),
   474  					zap.Error(err))
   475  			}
   476  
   477  			if s.logger.Enabled(zap.DebugLevel) {
   478  				s.logger.Debug("coordinator dnshard committed, txn committed",
   479  					util.TxnIDFieldWithID(txnMeta.ID))
   480  			}
   481  
   482  			txnCtx.changeStatusLocked(txn.TxnStatus_Committed)
   483  			s.releaseTxnContext(txnCtx)
   484  		}
   485  	})
   486  }
   487  
   488  func (s *service) checkCNRequest(request *txn.TxnRequest) {
   489  	if request.CNRequest == nil {
   490  		s.logger.Fatal("missing CNRequest")
   491  	}
   492  }
   493  
   494  func (s *service) waitClockTo(ts timestamp.Timestamp) {
   495  	for {
   496  		now, _ := runtime.ProcessLevelRuntime().Clock().Now()
   497  		if now.GreaterEq(ts) {
   498  			return
   499  		}
   500  		time.Sleep(time.Duration(ts.PhysicalTime + 1 - now.PhysicalTime))
   501  	}
   502  }