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 }