github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/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/rawtopiccommon" 18 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicreader" 19 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 20 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 21 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" 22 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 23 ) 24 25 var ( 26 PublicErrCommitSessionToExpiredSession = xerrors.Wrap(errors.New("ydb: commit to expired session")) 27 28 errCommitWithNilPartitionSession = xerrors.Wrap(errors.New("ydb: commit with nil partition session")) 29 ) 30 31 type partitionSessionID = rawtopicreader.PartitionSessionID 32 33 type topicStreamReaderImpl struct { 34 cfg topicStreamReaderConfig 35 ctx context.Context 36 cancel context.CancelFunc 37 38 freeBytes chan int 39 restBufferSizeBytes atomic.Int64 40 sessionController partitionSessionStorage 41 backgroundWorkers background.Worker 42 43 rawMessagesFromBuffer chan rawtopicreader.ServerMessage 44 45 batcher *batcher 46 committer *committer 47 48 stream RawTopicReaderStream 49 readConnectionID string 50 readerID int64 51 52 m xsync.RWMutex 53 err error 54 started bool 55 closed bool 56 } 57 58 type topicStreamReaderConfig struct { 59 CommitterBatchTimeLag time.Duration 60 CommitterBatchCounterTrigger int 61 BaseContext context.Context 62 BufferSizeProtoBytes int 63 Cred credentials.Credentials 64 CredUpdateInterval time.Duration 65 Consumer string 66 ReadSelectors []*PublicReadSelector 67 Trace *trace.Topic 68 GetPartitionStartOffsetCallback PublicGetPartitionStartOffsetFunc 69 CommitMode PublicCommitMode 70 Decoders decoderMap 71 } 72 73 func newTopicStreamReaderConfig() topicStreamReaderConfig { 74 return topicStreamReaderConfig{ 75 BaseContext: context.Background(), 76 BufferSizeProtoBytes: 1024 * 1024, 77 Cred: credentials.NewAnonymousCredentials(), 78 CredUpdateInterval: time.Hour, 79 CommitMode: CommitModeAsync, 80 CommitterBatchTimeLag: time.Second, 81 Decoders: newDecoderMap(), 82 Trace: &trace.Topic{}, 83 } 84 } 85 86 func (cfg *topicStreamReaderConfig) initMessage() *rawtopicreader.InitRequest { 87 res := &rawtopicreader.InitRequest{ 88 Consumer: cfg.Consumer, 89 } 90 91 res.TopicsReadSettings = make([]rawtopicreader.TopicReadSettings, len(cfg.ReadSelectors)) 92 for i, selector := range cfg.ReadSelectors { 93 settings := &res.TopicsReadSettings[i] 94 settings.Path = selector.Path 95 settings.PartitionsID = selector.Partitions 96 if !selector.ReadFrom.IsZero() { 97 settings.ReadFrom.HasValue = true 98 settings.ReadFrom.Value = selector.ReadFrom 99 } 100 if selector.MaxTimeLag != 0 { 101 settings.MaxLag.HasValue = true 102 settings.MaxLag.Value = selector.MaxTimeLag 103 } 104 } 105 106 return res 107 } 108 109 func newTopicStreamReader( 110 readerID int64, 111 stream RawTopicReaderStream, 112 cfg topicStreamReaderConfig, //nolint:gocritic 113 ) (_ *topicStreamReaderImpl, err error) { 114 defer func() { 115 if err != nil { 116 _ = stream.CloseSend() 117 } 118 }() 119 120 reader := newTopicStreamReaderStopped(readerID, stream, cfg) 121 if err = reader.initSession(); err != nil { 122 return nil, err 123 } 124 if err = reader.startLoops(); err != nil { 125 return nil, err 126 } 127 128 return reader, nil 129 } 130 131 func newTopicStreamReaderStopped( 132 readerID int64, 133 stream RawTopicReaderStream, 134 cfg topicStreamReaderConfig, //nolint:gocritic 135 ) *topicStreamReaderImpl { 136 labeledContext := pprof.WithLabels(cfg.BaseContext, pprof.Labels("base-context", "topic-stream-reader")) 137 stopPump, cancel := xcontext.WithCancel(labeledContext) 138 139 readerConnectionID, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) 140 if err != nil { 141 readerConnectionID = big.NewInt(-1) 142 } 143 144 res := &topicStreamReaderImpl{ 145 cfg: cfg, 146 ctx: stopPump, 147 freeBytes: make(chan int, 1), 148 stream: &syncedStream{stream: stream}, 149 cancel: cancel, 150 batcher: newBatcher(), 151 backgroundWorkers: *background.NewWorker(stopPump), 152 readConnectionID: "preinitID-" + readerConnectionID.String(), 153 readerID: readerID, 154 rawMessagesFromBuffer: make(chan rawtopicreader.ServerMessage, 1), 155 } 156 157 res.committer = newCommitter(cfg.Trace, labeledContext, cfg.CommitMode, res.send) 158 res.committer.BufferTimeLagTrigger = cfg.CommitterBatchTimeLag 159 res.committer.BufferCountTrigger = cfg.CommitterBatchCounterTrigger 160 res.sessionController.init() 161 res.freeBytes <- cfg.BufferSizeProtoBytes 162 163 return res 164 } 165 166 func (r *topicStreamReaderImpl) WaitInit(_ context.Context) error { 167 if !r.started { 168 return errors.New("not started: can be started only after initialize from constructor") 169 } 170 171 return nil 172 } 173 174 func (r *topicStreamReaderImpl) ReadMessageBatch( 175 ctx context.Context, 176 opts ReadMessageBatchOptions, 177 ) (batch *PublicBatch, err error) { 178 onDone := trace.TopicOnReaderReadMessages( 179 r.cfg.Trace, 180 ctx, 181 opts.MinCount, 182 opts.MaxCount, 183 r.getRestBufferBytes(), 184 ) 185 defer func() { 186 if batch == nil { 187 onDone(0, "", -1, -1, -1, -1, r.getRestBufferBytes(), err) 188 } else { 189 onDone( 190 len(batch.Messages), 191 batch.Topic(), 192 batch.PartitionID(), 193 batch.partitionSession().partitionSessionID.ToInt64(), 194 batch.commitRange.commitOffsetStart.ToInt64(), 195 batch.commitRange.commitOffsetEnd.ToInt64(), 196 r.getRestBufferBytes(), 197 err, 198 ) 199 } 200 }() 201 202 if err = ctx.Err(); err != nil { 203 return nil, err 204 } 205 206 defer func() { 207 if err == nil { 208 r.freeBufferFromMessages(batch) 209 } 210 }() 211 212 return r.consumeMessagesUntilBatch(ctx, opts) 213 } 214 215 func (r *topicStreamReaderImpl) consumeMessagesUntilBatch( 216 ctx context.Context, 217 opts ReadMessageBatchOptions, 218 ) (*PublicBatch, error) { 219 for { 220 item, err := r.batcher.Pop(ctx, opts.batcherGetOptions) 221 if err != nil { 222 return nil, err 223 } 224 225 switch { 226 case item.IsBatch(): 227 return item.Batch, nil 228 case item.IsRawMessage(): 229 r.sendRawMessageToChannelUnblocked(item.RawMessage) 230 default: 231 return nil, xerrors.WithStackTrace(fmt.Errorf("ydb: unexpected item type from batcher: %#v", item)) 232 } 233 } 234 } 235 236 func (r *topicStreamReaderImpl) sendRawMessageToChannelUnblocked(msg rawtopicreader.ServerMessage) { 237 select { 238 case r.rawMessagesFromBuffer <- msg: 239 return 240 default: 241 // send in goroutine, without block caller 242 r.backgroundWorkers.Start("sendMessageToRawChannel", func(ctx context.Context) { 243 select { 244 case r.rawMessagesFromBuffer <- msg: 245 case <-ctx.Done(): 246 } 247 }) 248 } 249 } 250 251 func (r *topicStreamReaderImpl) consumeRawMessageFromBuffer(ctx context.Context) { 252 doneChan := ctx.Done() 253 254 for { 255 var msg rawtopicreader.ServerMessage 256 select { 257 case <-doneChan: 258 return 259 case msg = <-r.rawMessagesFromBuffer: 260 // pass 261 } 262 263 switch m := msg.(type) { 264 case *rawtopicreader.StartPartitionSessionRequest: 265 if err := r.onStartPartitionSessionRequestFromBuffer(m); err != nil { 266 _ = r.CloseWithError(ctx, err) 267 268 return 269 } 270 case *rawtopicreader.StopPartitionSessionRequest: 271 if err := r.onStopPartitionSessionRequestFromBuffer(m); err != nil { 272 _ = r.CloseWithError(ctx, xerrors.WithStackTrace( 273 fmt.Errorf("ydb: unexpected error on stop partition handler: %w", err), 274 )) 275 276 return 277 } 278 case *rawtopicreader.PartitionSessionStatusResponse: 279 r.onPartitionSessionStatusResponseFromBuffer(ctx, m) 280 default: 281 _ = r.CloseWithError(ctx, xerrors.WithStackTrace( 282 fmt.Errorf("ydb: unexpected server message from buffer: %v", reflect.TypeOf(msg))), 283 ) 284 } 285 } 286 } 287 288 func (r *topicStreamReaderImpl) onStopPartitionSessionRequestFromBuffer( 289 msg *rawtopicreader.StopPartitionSessionRequest, 290 ) (err error) { 291 session, err := r.sessionController.Get(msg.PartitionSessionID) 292 if err != nil { 293 return err 294 } 295 296 onDone := trace.TopicOnReaderPartitionReadStopResponse( 297 r.cfg.Trace, 298 r.readConnectionID, 299 session.Context(), 300 session.Topic, 301 session.PartitionID, 302 session.partitionSessionID.ToInt64(), 303 msg.CommittedOffset.ToInt64(), 304 msg.Graceful, 305 ) 306 defer func() { 307 onDone(err) 308 }() 309 310 if msg.Graceful { 311 session.Close() 312 resp := &rawtopicreader.StopPartitionSessionResponse{ 313 PartitionSessionID: session.partitionSessionID, 314 } 315 if err = r.send(resp); err != nil { 316 return err 317 } 318 } 319 320 if _, err = r.sessionController.Remove(session.partitionSessionID); err != nil { 321 if msg.Graceful { 322 return err 323 } else { //nolint:revive,staticcheck 324 // double message with graceful=false is ok. 325 // It may be received after message with graceful=true and session was removed while process that. 326 327 // pass 328 } 329 } 330 331 return nil 332 } 333 334 func (r *topicStreamReaderImpl) onPartitionSessionStatusResponseFromBuffer( 335 ctx context.Context, 336 m *rawtopicreader.PartitionSessionStatusResponse, 337 ) { 338 panic("not implemented") 339 } 340 341 func (r *topicStreamReaderImpl) onUpdateTokenResponse(m *rawtopicreader.UpdateTokenResponse) { 342 } 343 344 func (r *topicStreamReaderImpl) Commit(ctx context.Context, commitRange commitRange) (err error) { 345 defer func() { 346 if errors.Is(err, PublicErrCommitSessionToExpiredSession) && r.cfg.CommitMode == CommitModeAsync { 347 err = nil 348 } 349 }() 350 351 if commitRange.partitionSession == nil { 352 return xerrors.WithStackTrace(errCommitWithNilPartitionSession) 353 } 354 355 session := commitRange.partitionSession 356 onDone := trace.TopicOnReaderCommit( 357 r.cfg.Trace, 358 ctx, 359 session.Topic, 360 session.PartitionID, 361 session.partitionSessionID.ToInt64(), 362 commitRange.commitOffsetStart.ToInt64(), 363 commitRange.commitOffsetEnd.ToInt64(), 364 ) 365 defer func() { 366 onDone(err) 367 }() 368 369 if err = r.checkCommitRange(commitRange); err != nil { 370 return err 371 } 372 373 return r.committer.Commit(ctx, commitRange) 374 } 375 376 func (r *topicStreamReaderImpl) checkCommitRange(commitRange commitRange) error { 377 if r.cfg.CommitMode == CommitModeNone { 378 return ErrCommitDisabled 379 } 380 session := commitRange.partitionSession 381 382 if session == nil { 383 return xerrors.WithStackTrace(errCommitWithNilPartitionSession) 384 } 385 386 if session.Context().Err() != nil { 387 return xerrors.WithStackTrace(PublicErrCommitSessionToExpiredSession) 388 } 389 390 ownSession, err := r.sessionController.Get(session.partitionSessionID) 391 if err != nil || session != ownSession { 392 return xerrors.WithStackTrace(PublicErrCommitSessionToExpiredSession) 393 } 394 if session.committedOffset() != commitRange.commitOffsetStart && r.cfg.CommitMode == CommitModeSync { 395 return ErrWrongCommitOrderInSyncMode 396 } 397 398 return nil 399 } 400 401 func (r *topicStreamReaderImpl) send(msg rawtopicreader.ClientMessage) error { 402 err := r.stream.Send(msg) 403 if err != nil { 404 trace.TopicOnReaderError(r.cfg.Trace, r.readConnectionID, err) 405 _ = r.CloseWithError(r.ctx, err) 406 } 407 408 return err 409 } 410 411 func (r *topicStreamReaderImpl) startLoops() error { 412 if err := r.setStarted(); err != nil { 413 return err 414 } 415 416 r.backgroundWorkers.Start("readMessagesLoop", r.readMessagesLoop) 417 r.backgroundWorkers.Start("dataRequestLoop", r.dataRequestLoop) 418 r.backgroundWorkers.Start("updateTokenLoop", r.updateTokenLoop) 419 420 r.backgroundWorkers.Start("consumeRawMessageFromBuffer", r.consumeRawMessageFromBuffer) 421 422 return nil 423 } 424 425 func (r *topicStreamReaderImpl) setStarted() error { 426 r.m.Lock() 427 defer r.m.Unlock() 428 429 if r.started { 430 return xerrors.WithStackTrace(errors.New("already started")) 431 } 432 433 r.started = true 434 435 return nil 436 } 437 438 func (r *topicStreamReaderImpl) initSession() (err error) { 439 initMessage := r.cfg.initMessage() 440 441 onDone := trace.TopicOnReaderInit(r.cfg.Trace, r.readConnectionID, initMessage) 442 defer func() { 443 onDone(r.readConnectionID, err) 444 }() 445 446 if err = r.send(initMessage); err != nil { 447 return err 448 } 449 450 resp, err := r.stream.Recv() 451 if err != nil { 452 return err 453 } 454 455 if status := resp.StatusData(); !status.Status.IsSuccess() { 456 return xerrors.WithStackTrace(fmt.Errorf("bad status on initial error: %v (%v)", status.Status, status.Issues)) 457 } 458 459 initResp, ok := resp.(*rawtopicreader.InitResponse) 460 if !ok { 461 return xerrors.WithStackTrace(fmt.Errorf("bad message type on session init: %v (%v)", resp, reflect.TypeOf(resp))) 462 } 463 464 r.readConnectionID = initResp.SessionID 465 466 return nil 467 } 468 469 func (r *topicStreamReaderImpl) addRestBufferBytes(delta int) int { 470 val := r.restBufferSizeBytes.Add(int64(delta)) 471 if val <= 0 { 472 r.batcher.IgnoreMinRestrictionsOnNextPop() 473 } 474 475 return int(val) 476 } 477 478 func (r *topicStreamReaderImpl) getRestBufferBytes() int { 479 return int(r.restBufferSizeBytes.Load()) 480 } 481 482 func (r *topicStreamReaderImpl) readMessagesLoop(ctx context.Context) { 483 ctx, cancel := xcontext.WithCancel(ctx) 484 defer cancel() 485 486 for { 487 serverMessage, err := r.stream.Recv() 488 if err != nil { 489 trace.TopicOnReaderError(r.cfg.Trace, r.readConnectionID, err) 490 if errors.Is(err, rawtopicreader.ErrUnexpectedMessageType) { 491 trace.TopicOnReaderUnknownGrpcMessage(r.cfg.Trace, r.readConnectionID, err) 492 // new messages can be added to protocol, it must be backward compatible to old programs 493 // and skip message is safe 494 continue 495 } 496 _ = r.CloseWithError(ctx, err) 497 498 return 499 } 500 501 status := serverMessage.StatusData() 502 if !status.Status.IsSuccess() { 503 _ = r.CloseWithError(ctx, 504 xerrors.WithStackTrace( 505 fmt.Errorf("ydb: bad status from pq grpc stream: %v, %v", status.Status, status.Issues.String()), 506 ), 507 ) 508 } 509 510 switch m := serverMessage.(type) { 511 case *rawtopicreader.ReadResponse: 512 if err = r.onReadResponse(m); err != nil { 513 _ = r.CloseWithError(ctx, err) 514 } 515 case *rawtopicreader.StartPartitionSessionRequest: 516 if err = r.onStartPartitionSessionRequest(m); err != nil { 517 _ = r.CloseWithError(ctx, err) 518 519 return 520 } 521 case *rawtopicreader.StopPartitionSessionRequest: 522 if err = r.onStopPartitionSessionRequest(m); err != nil { 523 _ = r.CloseWithError(ctx, err) 524 525 return 526 } 527 case *rawtopicreader.CommitOffsetResponse: 528 if err = r.onCommitResponse(m); err != nil { 529 _ = r.CloseWithError(ctx, err) 530 531 return 532 } 533 534 case *rawtopicreader.UpdateTokenResponse: 535 r.onUpdateTokenResponse(m) 536 default: 537 trace.TopicOnReaderUnknownGrpcMessage( 538 r.cfg.Trace, 539 r.readConnectionID, 540 xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf( 541 "ydb: unexpected message type in stream reader: %v", 542 reflect.TypeOf(serverMessage), 543 ))), 544 ) 545 } 546 } 547 } 548 549 func (r *topicStreamReaderImpl) dataRequestLoop(ctx context.Context) { 550 if r.ctx.Err() != nil { 551 return 552 } 553 554 doneChan := ctx.Done() 555 556 for { 557 select { 558 case <-doneChan: 559 _ = r.CloseWithError(ctx, r.ctx.Err()) 560 561 return 562 563 case free := <-r.freeBytes: 564 sum := free 565 566 // consume all messages from order and compress it to one data request 567 forConsumeRequests: 568 for { 569 select { 570 case free = <-r.freeBytes: 571 sum += free 572 default: 573 break forConsumeRequests 574 } 575 } 576 577 resCapacity := r.addRestBufferBytes(sum) 578 trace.TopicOnReaderSentDataRequest(r.cfg.Trace, r.readConnectionID, sum, resCapacity) 579 if err := r.sendDataRequest(sum); err != nil { 580 return 581 } 582 } 583 } 584 } 585 586 func (r *topicStreamReaderImpl) sendDataRequest(size int) error { 587 return r.send(&rawtopicreader.ReadRequest{BytesSize: size}) 588 } 589 590 func (r *topicStreamReaderImpl) freeBufferFromMessages(batch *PublicBatch) { 591 size := 0 592 for messageIndex := range batch.Messages { 593 size += batch.Messages[messageIndex].bufferBytesAccount 594 } 595 select { 596 case r.freeBytes <- size: 597 case <-r.ctx.Done(): 598 } 599 } 600 601 func (r *topicStreamReaderImpl) updateTokenLoop(ctx context.Context) { 602 ticker := time.NewTicker(r.cfg.CredUpdateInterval) 603 defer ticker.Stop() 604 605 readerCancel := ctx.Done() 606 for { 607 select { 608 case <-readerCancel: 609 return 610 case <-ticker.C: 611 r.updateToken(r.ctx) 612 } 613 } 614 } 615 616 func (r *topicStreamReaderImpl) onReadResponse(msg *rawtopicreader.ReadResponse) (err error) { 617 resCapacity := r.addRestBufferBytes(-msg.BytesSize) 618 onDone := trace.TopicOnReaderReceiveDataResponse(r.cfg.Trace, r.readConnectionID, resCapacity, msg) 619 defer func() { 620 onDone(err) 621 }() 622 623 batchesCount := 0 624 for i := range msg.PartitionData { 625 batchesCount += len(msg.PartitionData[i].Batches) 626 } 627 628 var batches []*PublicBatch 629 for pIndex := range msg.PartitionData { 630 p := &msg.PartitionData[pIndex] 631 632 // normal way 633 session, err := r.sessionController.Get(p.PartitionSessionID) 634 if err != nil { 635 return err 636 } 637 638 for bIndex := range p.Batches { 639 if r.ctx.Err() != nil { 640 return r.ctx.Err() 641 } 642 643 batch, err := newBatchFromStream(r.cfg.Decoders, session, p.Batches[bIndex]) 644 if err != nil { 645 return err 646 } 647 batches = append(batches, batch) 648 } 649 } 650 651 if err := splitBytesByMessagesInBatches(batches, msg.BytesSize); err != nil { 652 return err 653 } 654 655 for i := range batches { 656 if err := r.batcher.PushBatches(batches[i]); err != nil { 657 return err 658 } 659 } 660 661 return nil 662 } 663 664 func (r *topicStreamReaderImpl) CloseWithError(ctx context.Context, reason error) (closeErr error) { 665 onDone := trace.TopicOnReaderClose(r.cfg.Trace, r.readConnectionID, reason) 666 defer onDone(closeErr) 667 668 isFirstClose := false 669 r.m.WithLock(func() { 670 if r.closed { 671 return 672 } 673 isFirstClose = true 674 r.closed = true 675 676 r.err = reason 677 r.cancel() 678 }) 679 if !isFirstClose { 680 return nil 681 } 682 683 closeErr = r.committer.Close(ctx, reason) 684 685 batcherErr := r.batcher.Close(reason) 686 if closeErr == nil { 687 closeErr = batcherErr 688 } 689 690 // close stream strong after committer close - for flush commits buffer 691 streamCloseErr := r.stream.CloseSend() 692 if closeErr == nil { 693 closeErr = streamCloseErr 694 } 695 696 // close background workers after r.stream.CloseSend 697 bgCloseErr := r.backgroundWorkers.Close(ctx, reason) 698 if closeErr == nil { 699 closeErr = bgCloseErr 700 } 701 702 return closeErr 703 } 704 705 func (r *topicStreamReaderImpl) onCommitResponse(msg *rawtopicreader.CommitOffsetResponse) error { 706 for i := range msg.PartitionsCommittedOffsets { 707 commit := &msg.PartitionsCommittedOffsets[i] 708 partition, err := r.sessionController.Get(commit.PartitionSessionID) 709 if err != nil { 710 return fmt.Errorf("ydb: can't found session on commit response: %w", err) 711 } 712 partition.setCommittedOffset(commit.CommittedOffset) 713 714 trace.TopicOnReaderCommittedNotify( 715 r.cfg.Trace, 716 r.readConnectionID, 717 partition.Topic, 718 partition.PartitionID, 719 partition.partitionSessionID.ToInt64(), 720 commit.CommittedOffset.ToInt64(), 721 ) 722 723 r.committer.OnCommitNotify(partition, commit.CommittedOffset) 724 } 725 726 return nil 727 } 728 729 func (r *topicStreamReaderImpl) updateToken(ctx context.Context) { 730 onUpdateToken := trace.TopicOnReaderUpdateToken( 731 r.cfg.Trace, 732 r.readConnectionID, 733 ) 734 token, err := r.cfg.Cred.Token(ctx) 735 onSent := onUpdateToken(len(token), err) 736 if err != nil { 737 return 738 } 739 740 err = r.send(&rawtopicreader.UpdateTokenRequest{UpdateTokenRequest: rawtopiccommon.UpdateTokenRequest{Token: token}}) 741 onSent(err) 742 } 743 744 func (r *topicStreamReaderImpl) onStartPartitionSessionRequest(m *rawtopicreader.StartPartitionSessionRequest) error { 745 session := newPartitionSession( 746 r.ctx, 747 m.PartitionSession.Path, 748 m.PartitionSession.PartitionID, 749 r.readerID, 750 r.readConnectionID, 751 m.PartitionSession.PartitionSessionID, 752 m.CommittedOffset, 753 ) 754 if err := r.sessionController.Add(session); err != nil { 755 return err 756 } 757 758 return r.batcher.PushRawMessage(session, m) 759 } 760 761 func (r *topicStreamReaderImpl) onStartPartitionSessionRequestFromBuffer( 762 m *rawtopicreader.StartPartitionSessionRequest, 763 ) (err error) { 764 session, err := r.sessionController.Get(m.PartitionSession.PartitionSessionID) 765 if err != nil { 766 return err 767 } 768 769 onDone := trace.TopicOnReaderPartitionReadStartResponse( 770 r.cfg.Trace, 771 r.readConnectionID, 772 session.Context(), 773 session.Topic, 774 session.PartitionID, 775 session.partitionSessionID.ToInt64(), 776 ) 777 778 respMessage := &rawtopicreader.StartPartitionSessionResponse{ 779 PartitionSessionID: session.partitionSessionID, 780 } 781 782 var forceOffset *int64 783 var commitOffset *int64 784 785 defer func() { 786 onDone(forceOffset, commitOffset, err) 787 }() 788 789 if r.cfg.GetPartitionStartOffsetCallback != nil { 790 req := PublicGetPartitionStartOffsetRequest{ 791 Topic: session.Topic, 792 PartitionID: session.PartitionID, 793 } 794 resp, callbackErr := r.cfg.GetPartitionStartOffsetCallback(session.Context(), req) 795 if callbackErr != nil { 796 return callbackErr 797 } 798 if resp.startOffsetUsed { 799 wantOffset := resp.startOffset.ToInt64() 800 forceOffset = &wantOffset 801 } 802 } 803 804 respMessage.ReadOffset.FromInt64Pointer(forceOffset) 805 if r.cfg.CommitMode.commitsEnabled() { 806 commitOffset = forceOffset 807 respMessage.CommitOffset.FromInt64Pointer(commitOffset) 808 } 809 810 return r.send(respMessage) 811 } 812 813 func (r *topicStreamReaderImpl) onStopPartitionSessionRequest(m *rawtopicreader.StopPartitionSessionRequest) error { 814 session, err := r.sessionController.Get(m.PartitionSessionID) 815 if err != nil { 816 return err 817 } 818 819 if !m.Graceful { 820 session.Close() 821 } 822 823 return r.batcher.PushRawMessage(session, m) 824 }