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 }