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  }