github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/topic/topicreaderinternal/stream_reader_impl.go (about) 1 package topicreaderinternal 2 3 import ( 4 "context" 5 "crypto/rand" 6 "errors" 7 "fmt" 8 "math" 9 "math/big" 10 "reflect" 11 "runtime/pprof" 12 "sync/atomic" 13 "time" 14 15 "github.com/ydb-platform/ydb-go-sdk/v3/credentials" 16 "github.com/ydb-platform/ydb-go-sdk/v3/internal/background" 17 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic" 18 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopiccommon" 19 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicreader" 20 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawydb" 21 "github.com/ydb-platform/ydb-go-sdk/v3/internal/operation" 22 "github.com/ydb-platform/ydb-go-sdk/v3/internal/topic/topicreadercommon" 23 "github.com/ydb-platform/ydb-go-sdk/v3/internal/tx" 24 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 25 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 26 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" 27 "github.com/ydb-platform/ydb-go-sdk/v3/retry" 28 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 29 ) 30 31 var ( 32 errCommitWithNilPartitionSession = xerrors.Wrap(errors.New("ydb: commit with nil partition session")) 33 errUnexpectedEmptyConsumerName = xerrors.Wrap(errors.New("ydb: create ydb reader with empty consumer name. Set one of: consumer name or option WithReaderWithoutConsumer")) //nolint:lll 34 errCantCommitWithoutConsumer = xerrors.Wrap(errors.New("ydb: reader can't commit messages without consumer")) 35 errBufferSize = xerrors.Wrap(errors.New("ydb: buffer of topic reader must be greater than zero, see option topicoptions.WithReaderBufferSizeBytes")) //nolint:lll 36 errTopicSelectorsEmpty = xerrors.Wrap(errors.New("ydb: topic selector for topic reader is empty, see arguments on topic starts")) //nolint:lll 37 ) 38 39 var clientSessionCounter atomic.Int64 40 41 type partitionSessionID = rawtopicreader.PartitionSessionID 42 43 type topicStreamReaderImpl struct { 44 cfg topicStreamReaderConfig 45 ctx context.Context //nolint:containedctx 46 cancel context.CancelFunc 47 48 topicClient TopicClient 49 freeBytes chan int 50 restBufferSizeBytes atomic.Int64 51 sessionController topicreadercommon.PartitionSessionStorage 52 backgroundWorkers background.Worker 53 54 rawMessagesFromBuffer chan rawtopicreader.ServerMessage 55 56 batcher *batcher 57 committer *topicreadercommon.Committer 58 59 stream topicreadercommon.RawTopicReaderStream 60 readConnectionID string 61 readerID int64 62 63 m xsync.RWMutex 64 err error 65 started bool 66 closed bool 67 } 68 69 type topicStreamReaderConfig struct { 70 CommitterBatchTimeLag time.Duration 71 CommitterBatchCounterTrigger int 72 BaseContext context.Context //nolint:containedctx 73 BufferSizeProtoBytes int 74 Cred credentials.Credentials 75 CredUpdateInterval time.Duration 76 Consumer string 77 ReadWithoutConsumer bool 78 ReadSelectors []*topicreadercommon.PublicReadSelector 79 Trace *trace.Topic 80 GetPartitionStartOffsetCallback PublicGetPartitionStartOffsetFunc 81 CommitMode topicreadercommon.PublicCommitMode 82 Decoders topicreadercommon.DecoderMap 83 } 84 85 func newTopicStreamReaderConfig() topicStreamReaderConfig { 86 return topicStreamReaderConfig{ 87 BaseContext: context.Background(), 88 BufferSizeProtoBytes: topicreadercommon.DefaultBufferSize, 89 Cred: credentials.NewAnonymousCredentials(), 90 CredUpdateInterval: time.Hour, 91 CommitMode: topicreadercommon.CommitModeAsync, 92 CommitterBatchTimeLag: time.Second, 93 Decoders: topicreadercommon.NewDecoderMap(), 94 Trace: &trace.Topic{}, 95 } 96 } 97 98 func (cfg *topicStreamReaderConfig) Validate() []error { 99 var validateErrors []error 100 101 if cfg.Consumer != "" && cfg.ReadWithoutConsumer { 102 validateErrors = append(validateErrors, errSetConsumerAndNoConsumer) 103 } 104 if cfg.Consumer == "" && !cfg.ReadWithoutConsumer { 105 validateErrors = append(validateErrors, errUnexpectedEmptyConsumerName) 106 } 107 if cfg.ReadWithoutConsumer && cfg.CommitMode != topicreadercommon.CommitModeNone { 108 validateErrors = append(validateErrors, errCantCommitWithoutConsumer) 109 } 110 if cfg.BufferSizeProtoBytes <= 0 { 111 validateErrors = append(validateErrors, errBufferSize) 112 } 113 if len(cfg.ReadSelectors) == 0 { 114 validateErrors = append(validateErrors, errTopicSelectorsEmpty) 115 } 116 117 return validateErrors 118 } 119 120 func newTopicStreamReader( 121 client TopicClient, 122 readerID int64, 123 stream topicreadercommon.RawTopicReaderStream, 124 cfg topicStreamReaderConfig, //nolint:gocritic 125 ) (_ *topicStreamReaderImpl, err error) { 126 defer func() { 127 if err != nil { 128 _ = stream.CloseSend() 129 } 130 }() 131 132 reader := newTopicStreamReaderStopped(client, readerID, stream, cfg) 133 if err = reader.initSession(); err != nil { 134 return nil, err 135 } 136 if err = reader.startBackgroundWorkers(); err != nil { 137 return nil, err 138 } 139 140 return reader, nil 141 } 142 143 func newTopicStreamReaderStopped( 144 client TopicClient, 145 readerID int64, 146 stream topicreadercommon.RawTopicReaderStream, 147 cfg topicStreamReaderConfig, //nolint:gocritic 148 ) *topicStreamReaderImpl { 149 labeledContext := pprof.WithLabels(cfg.BaseContext, pprof.Labels("base-context", "topic-stream-reader")) 150 stopPump, cancel := xcontext.WithCancel(labeledContext) 151 152 readerConnectionID, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) 153 if err != nil { 154 readerConnectionID = big.NewInt(-1) 155 } 156 157 res := &topicStreamReaderImpl{ 158 cfg: cfg, 159 ctx: stopPump, 160 topicClient: client, 161 freeBytes: make(chan int, 1), 162 stream: topicreadercommon.NewSyncedStream(stream), 163 cancel: cancel, 164 batcher: newBatcher(), 165 readConnectionID: "preinitID-" + readerConnectionID.String(), 166 readerID: readerID, 167 rawMessagesFromBuffer: make(chan rawtopicreader.ServerMessage, 1), 168 } 169 170 res.backgroundWorkers = *background.NewWorker(stopPump, "topic-reader-stream-background") 171 172 res.committer = topicreadercommon.NewCommitterStopped(cfg.Trace, labeledContext, cfg.CommitMode, res.send) 173 res.committer.BufferTimeLagTrigger = cfg.CommitterBatchTimeLag 174 res.committer.BufferCountTrigger = cfg.CommitterBatchCounterTrigger 175 res.freeBytes <- cfg.BufferSizeProtoBytes 176 177 return res 178 } 179 180 func (r *topicStreamReaderImpl) WaitInit(_ context.Context) error { 181 if !r.started { 182 return errors.New("not started: can be started only after initialize from constructor") 183 } 184 185 return nil 186 } 187 188 func (r *topicStreamReaderImpl) PopMessagesBatchTx( 189 ctx context.Context, 190 tx tx.Transaction, 191 opts ReadMessageBatchOptions, 192 ) (_ *topicreadercommon.PublicBatch, resErr error) { 193 traceCtx := ctx 194 onDone := trace.TopicOnReaderStreamPopBatchTx( 195 r.cfg.Trace, 196 &traceCtx, 197 r.readerID, 198 r.readConnectionID, 199 tx.SessionID(), 200 tx, 201 ) 202 ctx = traceCtx 203 defer func() { 204 onDone(resErr) 205 }() 206 207 batch, err := r.ReadMessageBatch(ctx, opts) 208 if err != nil { 209 return nil, err 210 } 211 212 if err = r.commitWithTransaction(ctx, tx, batch); err == nil { 213 return batch, nil 214 } 215 216 return nil, err 217 } 218 219 func (r *topicStreamReaderImpl) commitWithTransaction( 220 ctx context.Context, 221 tx tx.Transaction, 222 batch *topicreadercommon.PublicBatch, 223 ) error { 224 if err := tx.UnLazy(ctx); err != nil { 225 return fmt.Errorf("ydb: failed to materialize transaction: %w", err) 226 } 227 228 req := r.createUpdateOffsetRequest(ctx, batch, tx) 229 updateOffesetInTransactionErr := retry.Retry(ctx, func(ctx context.Context) (err error) { 230 traceCtx := ctx 231 onDone := trace.TopicOnReaderUpdateOffsetsInTransaction( 232 r.cfg.Trace, 233 &traceCtx, 234 r.readerID, 235 r.readConnectionID, 236 tx.SessionID(), 237 tx, 238 ) 239 defer func() { 240 onDone(err) 241 }() 242 243 ctx = traceCtx 244 err = r.topicClient.UpdateOffsetsInTransaction(ctx, req) 245 246 return err 247 }) 248 if updateOffesetInTransactionErr == nil { 249 r.addOnTransactionCompletedHandler(ctx, tx, batch, updateOffesetInTransactionErr) 250 } else { 251 _ = retry.Retry(ctx, func(ctx context.Context) (err error) { 252 traceCtx := ctx 253 onDone := trace.TopicOnReaderTransactionRollback( 254 r.cfg.Trace, 255 &traceCtx, 256 r.readerID, 257 r.readConnectionID, 258 tx.SessionID(), 259 tx, 260 ) 261 ctx = traceCtx 262 defer func() { 263 onDone(err) 264 }() 265 266 return tx.Rollback(ctx) 267 }) 268 269 _ = r.CloseWithError(xcontext.ValueOnly(ctx), xerrors.WithStackTrace(xerrors.Retryable( 270 fmt.Errorf("ydb: failed add topic offsets in transaction: %w", updateOffesetInTransactionErr), 271 ))) 272 273 return updateOffesetInTransactionErr 274 } 275 276 return nil 277 } 278 279 func (r *topicStreamReaderImpl) addOnTransactionCompletedHandler( 280 ctx context.Context, 281 tx tx.Transaction, 282 batch *topicreadercommon.PublicBatch, 283 updateOffesetInTransactionErr error, 284 ) { 285 commitRange := topicreadercommon.GetCommitRange(batch) 286 tx.OnCompleted(func(transactionResult error) { 287 traceCtx := ctx 288 onDone := trace.TopicOnReaderTransactionCompleted( 289 r.cfg.Trace, 290 &traceCtx, 291 r.readerID, 292 r.readConnectionID, 293 tx.SessionID(), 294 tx, 295 transactionResult, 296 ) 297 defer onDone() 298 299 ctx = traceCtx 300 if transactionResult == nil { 301 topicreadercommon.BatchGetPartitionSession(batch).SetCommittedOffsetForward(commitRange.CommitOffsetEnd) 302 } else { 303 _ = r.CloseWithError(xcontext.ValueOnly(ctx), xerrors.WithStackTrace(xerrors.RetryableError( 304 fmt.Errorf("ydb: failed batch commit because transaction doesn't committed: %w", updateOffesetInTransactionErr), 305 ))) 306 } 307 }) 308 } 309 310 func (r *topicStreamReaderImpl) createUpdateOffsetRequest( 311 ctx context.Context, 312 batch *topicreadercommon.PublicBatch, 313 tx tx.Transaction, 314 ) *rawtopic.UpdateOffsetsInTransactionRequest { 315 commitRange := topicreadercommon.GetCommitRange(batch) 316 317 return &rawtopic.UpdateOffsetsInTransactionRequest{ 318 OperationParams: rawydb.NewRawOperationParamsFromProto(operation.Params(ctx, 0, 0, operation.ModeSync)), 319 Tx: rawtopiccommon.TransactionIdentity{ 320 ID: tx.ID(), 321 Session: tx.SessionID(), 322 }, 323 Topics: []rawtopic.UpdateOffsetsInTransactionRequest_TopicOffsets{ 324 { 325 Path: batch.Topic(), 326 Partitions: []rawtopic.UpdateOffsetsInTransactionRequest_PartitionOffsets{ 327 { 328 PartitionID: batch.PartitionID(), 329 PartitionOffsets: []rawtopiccommon.OffsetRange{ 330 { 331 Start: commitRange.CommitOffsetStart, 332 End: commitRange.CommitOffsetEnd, 333 }, 334 }, 335 }, 336 }, 337 }, 338 }, 339 Consumer: r.cfg.Consumer, 340 } 341 } 342 343 func (r *topicStreamReaderImpl) ReadMessageBatch( 344 ctx context.Context, 345 opts ReadMessageBatchOptions, 346 ) (batch *topicreadercommon.PublicBatch, err error) { 347 onDone := trace.TopicOnReaderReadMessages( 348 r.cfg.Trace, 349 &ctx, 350 opts.MinCount, 351 opts.MaxCount, 352 r.getRestBufferBytes(), 353 ) 354 defer func() { 355 if batch == nil { 356 onDone(0, "", -1, -1, -1, -1, r.getRestBufferBytes(), err) 357 } else { 358 commitRange := topicreadercommon.GetCommitRange(batch) 359 onDone( 360 len(batch.Messages), 361 batch.Topic(), 362 batch.PartitionID(), 363 topicreadercommon.BatchGetPartitionSession(batch).StreamPartitionSessionID.ToInt64(), 364 commitRange.CommitOffsetStart.ToInt64(), 365 commitRange.CommitOffsetEnd.ToInt64(), 366 r.getRestBufferBytes(), 367 err, 368 ) 369 } 370 }() 371 372 if err = ctx.Err(); err != nil { 373 return nil, err 374 } 375 376 defer func() { 377 if err == nil { 378 r.freeBufferFromMessages(batch) 379 } 380 }() 381 382 return r.consumeMessagesUntilBatch(ctx, opts) 383 } 384 385 func (r *topicStreamReaderImpl) consumeMessagesUntilBatch( 386 ctx context.Context, 387 opts ReadMessageBatchOptions, 388 ) (*topicreadercommon.PublicBatch, error) { 389 for { 390 item, err := r.batcher.Pop(ctx, opts.batcherGetOptions) 391 if err != nil { 392 return nil, err 393 } 394 395 switch { 396 case item.IsBatch(): 397 return item.Batch, nil 398 case item.IsRawMessage(): 399 r.sendRawMessageToChannelUnblocked(item.RawMessage) 400 default: 401 return nil, xerrors.WithStackTrace(fmt.Errorf("ydb: unexpected item type from batcher: %#v", item)) 402 } 403 } 404 } 405 406 func (r *topicStreamReaderImpl) sendRawMessageToChannelUnblocked(msg rawtopicreader.ServerMessage) { 407 select { 408 case r.rawMessagesFromBuffer <- msg: 409 return 410 default: 411 // send in goroutine, without block caller 412 r.backgroundWorkers.Start("sendMessageToRawChannel", func(ctx context.Context) { 413 select { 414 case r.rawMessagesFromBuffer <- msg: 415 case <-ctx.Done(): 416 } 417 }) 418 } 419 } 420 421 func (r *topicStreamReaderImpl) consumeRawMessageFromBuffer(ctx context.Context) { 422 doneChan := ctx.Done() 423 424 for { 425 var msg rawtopicreader.ServerMessage 426 select { 427 case <-doneChan: 428 return 429 case msg = <-r.rawMessagesFromBuffer: 430 // pass 431 } 432 433 switch m := msg.(type) { 434 case *rawtopicreader.StartPartitionSessionRequest: 435 if err := r.onStartPartitionSessionRequestFromBuffer(m); err != nil { 436 _ = r.CloseWithError(ctx, err) 437 438 return 439 } 440 case *rawtopicreader.StopPartitionSessionRequest: 441 if err := r.onStopPartitionSessionRequestFromBuffer(m); err != nil { 442 _ = r.CloseWithError(ctx, xerrors.WithStackTrace( 443 fmt.Errorf("ydb: unexpected error on stop partition handler: %w", err), 444 )) 445 446 return 447 } 448 case *rawtopicreader.PartitionSessionStatusResponse: 449 r.onPartitionSessionStatusResponseFromBuffer(ctx, m) 450 default: 451 _ = r.CloseWithError(ctx, xerrors.WithStackTrace( 452 fmt.Errorf("ydb: unexpected server message from buffer: %v", reflect.TypeOf(msg))), 453 ) 454 } 455 } 456 } 457 458 func (r *topicStreamReaderImpl) onStopPartitionSessionRequestFromBuffer( 459 msg *rawtopicreader.StopPartitionSessionRequest, 460 ) (err error) { 461 session, err := r.sessionController.Get(msg.PartitionSessionID) 462 if err != nil { 463 return err 464 } 465 466 onDone := trace.TopicOnReaderPartitionReadStopResponse( 467 r.cfg.Trace, 468 r.readConnectionID, 469 session.Context(), 470 session.Topic, 471 session.PartitionID, 472 session.StreamPartitionSessionID.ToInt64(), 473 msg.CommittedOffset.ToInt64(), 474 msg.Graceful, 475 ) 476 defer func() { 477 onDone(err) 478 }() 479 480 if msg.Graceful { 481 session.Close() 482 resp := &rawtopicreader.StopPartitionSessionResponse{ 483 PartitionSessionID: session.StreamPartitionSessionID, 484 } 485 if err = r.send(resp); err != nil { 486 return err 487 } 488 } 489 490 if _, err = r.sessionController.Remove(session.StreamPartitionSessionID); err != nil { 491 if msg.Graceful { 492 return err 493 } else { //nolint:revive,staticcheck 494 // double message with graceful=false is ok. 495 // It may be received after message with graceful=true and session was removed while process that. 496 497 // pass 498 } 499 } 500 501 return nil 502 } 503 504 func (r *topicStreamReaderImpl) onPartitionSessionStatusResponseFromBuffer( 505 ctx context.Context, 506 m *rawtopicreader.PartitionSessionStatusResponse, 507 ) { 508 panic("not implemented") 509 } 510 511 func (r *topicStreamReaderImpl) onUpdateTokenResponse(m *rawtopicreader.UpdateTokenResponse) { 512 } 513 514 func (r *topicStreamReaderImpl) Commit(ctx context.Context, commitRange topicreadercommon.CommitRange) (err error) { 515 defer func() { 516 if errors.Is( 517 err, 518 topicreadercommon.PublicErrCommitSessionToExpiredSession, 519 ) && r.cfg.CommitMode == topicreadercommon.CommitModeAsync { 520 err = nil 521 } 522 }() 523 524 if commitRange.PartitionSession == nil { 525 return xerrors.WithStackTrace(errCommitWithNilPartitionSession) 526 } 527 528 session := commitRange.PartitionSession 529 onDone := trace.TopicOnReaderCommit( 530 r.cfg.Trace, 531 &ctx, 532 session.Topic, 533 session.PartitionID, 534 session.StreamPartitionSessionID.ToInt64(), 535 commitRange.CommitOffsetStart.ToInt64(), 536 commitRange.CommitOffsetEnd.ToInt64(), 537 ) 538 defer func() { 539 onDone(err) 540 }() 541 542 if err = r.checkCommitRange(commitRange); err != nil { 543 return err 544 } 545 546 return r.committer.Commit(ctx, commitRange) 547 } 548 549 func (r *topicStreamReaderImpl) checkCommitRange(commitRange topicreadercommon.CommitRange) error { 550 if r.cfg.CommitMode == topicreadercommon.CommitModeNone { 551 return topicreadercommon.ErrCommitDisabled 552 } 553 session := commitRange.PartitionSession 554 555 if session == nil { 556 return xerrors.WithStackTrace(errCommitWithNilPartitionSession) 557 } 558 559 if session.Context().Err() != nil { 560 return xerrors.WithStackTrace(topicreadercommon.PublicErrCommitSessionToExpiredSession) 561 } 562 563 ownSession, err := r.sessionController.Get(session.StreamPartitionSessionID) 564 if err != nil || session != ownSession { 565 return xerrors.WithStackTrace(topicreadercommon.PublicErrCommitSessionToExpiredSession) 566 } 567 if session.CommittedOffset() != commitRange.CommitOffsetStart && r.cfg.CommitMode == topicreadercommon.CommitModeSync { 568 return topicreadercommon.ErrWrongCommitOrderInSyncMode 569 } 570 571 return nil 572 } 573 574 func (r *topicStreamReaderImpl) send(msg rawtopicreader.ClientMessage) error { 575 err := r.stream.Send(msg) 576 if err != nil { 577 trace.TopicOnReaderError(r.cfg.Trace, r.readConnectionID, err) 578 _ = r.CloseWithError(r.ctx, err) 579 } 580 581 return err 582 } 583 584 func (r *topicStreamReaderImpl) startBackgroundWorkers() error { 585 if err := r.setStarted(); err != nil { 586 return err 587 } 588 589 r.committer.Start() 590 591 r.backgroundWorkers.Start("readMessagesLoop", r.readMessagesLoop) 592 r.backgroundWorkers.Start("dataRequestLoop", r.dataRequestLoop) 593 r.backgroundWorkers.Start("updateTokenLoop", r.updateTokenLoop) 594 595 r.backgroundWorkers.Start("consumeRawMessageFromBuffer", r.consumeRawMessageFromBuffer) 596 597 return nil 598 } 599 600 func (r *topicStreamReaderImpl) setStarted() error { 601 r.m.Lock() 602 defer r.m.Unlock() 603 604 if r.started { 605 return xerrors.WithStackTrace(errors.New("already started")) 606 } 607 608 r.started = true 609 610 return nil 611 } 612 613 func (r *topicStreamReaderImpl) initSession() (err error) { 614 initMessage := topicreadercommon.CreateInitMessage(r.cfg.Consumer, r.cfg.ReadSelectors) 615 616 onDone := trace.TopicOnReaderInit(r.cfg.Trace, r.readConnectionID, initMessage) 617 defer func() { 618 onDone(r.readConnectionID, err) 619 }() 620 621 if err = r.send(initMessage); err != nil { 622 return err 623 } 624 625 resp, err := r.stream.Recv() 626 if err != nil { 627 return err 628 } 629 630 if status := resp.StatusData(); !status.Status.IsSuccess() { 631 // Need wrap status to common ydb operational error 632 // https://github.com/ydb-platform/ydb-go-sdk/issues/1361 633 return xerrors.WithStackTrace(fmt.Errorf("bad status on initial error: %v (%v)", status.Status, status.Issues)) 634 } 635 636 initResp, ok := resp.(*rawtopicreader.InitResponse) 637 if !ok { 638 return xerrors.WithStackTrace(fmt.Errorf("bad message type on session init: %v (%v)", resp, reflect.TypeOf(resp))) 639 } 640 641 r.readConnectionID = initResp.SessionID 642 643 return nil 644 } 645 646 func (r *topicStreamReaderImpl) addRestBufferBytes(delta int) int { 647 val := r.restBufferSizeBytes.Add(int64(delta)) 648 if val <= 0 { 649 r.batcher.IgnoreMinRestrictionsOnNextPop() 650 } 651 652 return int(val) 653 } 654 655 func (r *topicStreamReaderImpl) getRestBufferBytes() int { 656 return int(r.restBufferSizeBytes.Load()) 657 } 658 659 //nolint:funlen 660 func (r *topicStreamReaderImpl) readMessagesLoop(ctx context.Context) { 661 ctx, cancel := xcontext.WithCancel(ctx) 662 defer cancel() 663 664 for { 665 serverMessage, err := r.stream.Recv() 666 if err != nil { 667 trace.TopicOnReaderError(r.cfg.Trace, r.readConnectionID, err) 668 if errors.Is(err, rawtopicreader.ErrUnexpectedMessageType) { 669 trace.TopicOnReaderUnknownGrpcMessage(r.cfg.Trace, r.readConnectionID, err) 670 // new messages can be added to protocol, it must be backward compatible to old programs 671 // and skip message is safe 672 continue 673 } 674 _ = r.CloseWithError(ctx, err) 675 676 return 677 } 678 679 status := serverMessage.StatusData() 680 if !status.Status.IsSuccess() { 681 _ = r.CloseWithError(ctx, 682 xerrors.WithStackTrace( 683 fmt.Errorf("ydb: bad status from pq grpc stream: %v, %v", status.Status, status.Issues.String()), 684 ), 685 ) 686 } 687 688 switch m := serverMessage.(type) { 689 case *rawtopicreader.ReadResponse: 690 if err = r.onReadResponse(m); err != nil { 691 _ = r.CloseWithError(ctx, err) 692 } 693 case *rawtopicreader.StartPartitionSessionRequest: 694 if err = r.onStartPartitionSessionRequest(m); err != nil { 695 _ = r.CloseWithError(ctx, err) 696 697 return 698 } 699 case *rawtopicreader.StopPartitionSessionRequest: 700 if err = r.onStopPartitionSessionRequest(m); err != nil { 701 _ = r.CloseWithError(ctx, err) 702 703 return 704 } 705 case *rawtopicreader.CommitOffsetResponse: 706 if err = r.onCommitResponse(m); err != nil { 707 _ = r.CloseWithError(ctx, err) 708 709 return 710 } 711 712 case *rawtopicreader.UpdateTokenResponse: 713 r.onUpdateTokenResponse(m) 714 default: 715 trace.TopicOnReaderUnknownGrpcMessage( 716 r.cfg.Trace, 717 r.readConnectionID, 718 xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf( 719 "ydb: unexpected message type in stream reader: %v", 720 reflect.TypeOf(serverMessage), 721 ))), 722 ) 723 } 724 } 725 } 726 727 func (r *topicStreamReaderImpl) dataRequestLoop(ctx context.Context) { 728 if r.ctx.Err() != nil { 729 return 730 } 731 732 doneChan := ctx.Done() 733 734 for { 735 select { 736 case <-doneChan: 737 _ = r.CloseWithError(ctx, r.ctx.Err()) 738 739 return 740 741 case free := <-r.freeBytes: 742 sum := free 743 744 // consume all messages from order and compress it to one data request 745 forConsumeRequests: 746 for { 747 select { 748 case free = <-r.freeBytes: 749 sum += free 750 default: 751 break forConsumeRequests 752 } 753 } 754 755 resCapacity := r.addRestBufferBytes(sum) 756 trace.TopicOnReaderSentDataRequest(r.cfg.Trace, r.readConnectionID, sum, resCapacity) 757 if err := r.sendDataRequest(sum); err != nil { 758 return 759 } 760 } 761 } 762 } 763 764 func (r *topicStreamReaderImpl) sendDataRequest(size int) error { 765 return r.send(&rawtopicreader.ReadRequest{BytesSize: size}) 766 } 767 768 func (r *topicStreamReaderImpl) freeBufferFromMessages(batch *topicreadercommon.PublicBatch) { 769 size := 0 770 for messageIndex := range batch.Messages { 771 size += topicreadercommon.MessageGetBufferBytesAccount(batch.Messages[messageIndex]) 772 } 773 select { 774 case r.freeBytes <- size: 775 case <-r.ctx.Done(): 776 } 777 } 778 779 func (r *topicStreamReaderImpl) updateTokenLoop(ctx context.Context) { 780 ticker := time.NewTicker(r.cfg.CredUpdateInterval) 781 defer ticker.Stop() 782 783 readerCancel := ctx.Done() 784 for { 785 select { 786 case <-readerCancel: 787 return 788 case <-ticker.C: 789 r.updateToken(r.ctx) 790 } 791 } 792 } 793 794 func (r *topicStreamReaderImpl) onReadResponse(msg *rawtopicreader.ReadResponse) (err error) { 795 resCapacity := r.addRestBufferBytes(-msg.BytesSize) 796 onDone := trace.TopicOnReaderReceiveDataResponse(r.cfg.Trace, r.readConnectionID, resCapacity, msg) 797 defer func() { 798 onDone(err) 799 }() 800 801 batches, err2 := topicreadercommon.ReadRawBatchesToPublicBatches(msg, &r.sessionController, r.cfg.Decoders) 802 if err2 != nil { 803 return err2 804 } 805 806 for i := range batches { 807 if err := r.batcher.PushBatches(batches[i]); err != nil { 808 return err 809 } 810 } 811 812 return nil 813 } 814 815 func (r *topicStreamReaderImpl) CloseWithError(ctx context.Context, reason error) (closeErr error) { 816 onDone := trace.TopicOnReaderClose(r.cfg.Trace, r.readConnectionID, reason) 817 defer onDone(closeErr) 818 819 isFirstClose := false 820 r.m.WithLock(func() { 821 if r.closed { 822 return 823 } 824 isFirstClose = true 825 r.closed = true 826 827 r.err = reason 828 r.cancel() 829 }) 830 if !isFirstClose { 831 return nil 832 } 833 834 closeErr = r.committer.Close(ctx, reason) 835 836 batcherErr := r.batcher.Close(reason) 837 if closeErr == nil { 838 closeErr = batcherErr 839 } 840 841 // close stream strong after committer close - for flush commits buffer 842 streamCloseErr := r.stream.CloseSend() 843 if closeErr == nil { 844 closeErr = streamCloseErr 845 } 846 847 // close background workers after r.stream.CloseSend 848 bgCloseErr := r.backgroundWorkers.Close(ctx, reason) 849 if closeErr == nil { 850 closeErr = bgCloseErr 851 } 852 853 return closeErr 854 } 855 856 func (r *topicStreamReaderImpl) onCommitResponse(msg *rawtopicreader.CommitOffsetResponse) error { 857 for i := range msg.PartitionsCommittedOffsets { 858 commit := &msg.PartitionsCommittedOffsets[i] 859 partition, err := r.sessionController.Get(commit.PartitionSessionID) 860 if err != nil { 861 return fmt.Errorf("ydb: can't found session on commit response: %w", err) 862 } 863 partition.SetCommittedOffsetForward(commit.CommittedOffset) 864 865 trace.TopicOnReaderCommittedNotify( 866 r.cfg.Trace, 867 r.readConnectionID, 868 partition.Topic, 869 partition.PartitionID, 870 partition.StreamPartitionSessionID.ToInt64(), 871 commit.CommittedOffset.ToInt64(), 872 ) 873 874 r.committer.OnCommitNotify(partition, commit.CommittedOffset) 875 } 876 877 return nil 878 } 879 880 func (r *topicStreamReaderImpl) updateToken(ctx context.Context) { 881 onUpdateToken := trace.TopicOnReaderUpdateToken( 882 r.cfg.Trace, 883 r.readConnectionID, 884 ) 885 token, err := r.cfg.Cred.Token(ctx) 886 onSent := onUpdateToken(len(token), err) 887 if err != nil { 888 return 889 } 890 891 err = r.send(&rawtopicreader.UpdateTokenRequest{UpdateTokenRequest: rawtopiccommon.UpdateTokenRequest{Token: token}}) 892 onSent(err) 893 } 894 895 func (r *topicStreamReaderImpl) onStartPartitionSessionRequest(m *rawtopicreader.StartPartitionSessionRequest) error { 896 session := topicreadercommon.NewPartitionSession( 897 r.ctx, 898 m.PartitionSession.Path, 899 m.PartitionSession.PartitionID, 900 r.readerID, 901 r.readConnectionID, 902 m.PartitionSession.PartitionSessionID, 903 clientSessionCounter.Add(1), 904 m.CommittedOffset, 905 ) 906 if err := r.sessionController.Add(session); err != nil { 907 return err 908 } 909 910 return r.batcher.PushRawMessage(session, m) 911 } 912 913 func (r *topicStreamReaderImpl) onStartPartitionSessionRequestFromBuffer( 914 m *rawtopicreader.StartPartitionSessionRequest, 915 ) (err error) { 916 session, err := r.sessionController.Get(m.PartitionSession.PartitionSessionID) 917 if err != nil { 918 return err 919 } 920 921 var ( 922 ctx = session.Context() 923 onDone = trace.TopicOnReaderPartitionReadStartResponse( 924 r.cfg.Trace, 925 r.readConnectionID, 926 &ctx, 927 session.Topic, 928 session.PartitionID, 929 session.StreamPartitionSessionID.ToInt64(), 930 ) 931 ) 932 933 respMessage := &rawtopicreader.StartPartitionSessionResponse{ 934 PartitionSessionID: session.StreamPartitionSessionID, 935 } 936 937 var forceOffset *int64 938 var commitOffset *int64 939 940 defer func() { 941 onDone(forceOffset, commitOffset, err) 942 }() 943 944 if r.cfg.GetPartitionStartOffsetCallback != nil { 945 req := PublicGetPartitionStartOffsetRequest{ 946 Topic: session.Topic, 947 PartitionID: session.PartitionID, 948 } 949 resp, callbackErr := r.cfg.GetPartitionStartOffsetCallback(session.Context(), req) 950 if callbackErr != nil { 951 return callbackErr 952 } 953 if resp.startOffsetUsed { 954 wantOffset := resp.startOffset.ToInt64() 955 forceOffset = &wantOffset 956 } 957 } 958 959 respMessage.ReadOffset.FromInt64Pointer(forceOffset) 960 if r.cfg.CommitMode.CommitsEnabled() { 961 commitOffset = forceOffset 962 respMessage.CommitOffset.FromInt64Pointer(commitOffset) 963 } 964 965 return r.send(respMessage) 966 } 967 968 func (r *topicStreamReaderImpl) onStopPartitionSessionRequest(m *rawtopicreader.StopPartitionSessionRequest) error { 969 session, err := r.sessionController.Get(m.PartitionSessionID) 970 if err != nil { 971 return err 972 } 973 974 if !m.Graceful { 975 session.Close() 976 } 977 978 return r.batcher.PushRawMessage(session, m) 979 }