github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/topic/topicreaderinternal/reader.go (about)

     1  package topicreaderinternal
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  
     9  	"github.com/ydb-platform/ydb-go-sdk/v3/credentials"
    10  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/config"
    11  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/topic"
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/topic/topicreadercommon"
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/tx"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    16  )
    17  
    18  var (
    19  	errUnconnected = xerrors.Retryable(xerrors.Wrap(
    20  		errors.New("ydb: first connection attempt not finished"),
    21  	))
    22  	errReaderClosed                 = xerrors.Wrap(errors.New("ydb: reader closed"))
    23  	errSetConsumerAndNoConsumer     = xerrors.Wrap(errors.New("ydb: reader has non empty consumer name and set option WithReaderWithoutConsumer. Only one of them must be set")) //nolint:lll
    24  	errCommitSessionFromOtherReader = xerrors.Wrap(errors.New("ydb: commit with session from other reader"))
    25  )
    26  
    27  // TopicSteamReaderConnect connect to grpc stream
    28  // when connectionCtx closed stream must stop work and return errors for all methods
    29  type TopicSteamReaderConnect func(connectionCtx context.Context) (topicreadercommon.RawTopicReaderStream, error)
    30  
    31  type Reader struct {
    32  	reader             batchedStreamReader
    33  	defaultBatchConfig ReadMessageBatchOptions
    34  	tracer             *trace.Topic
    35  	readerID           int64
    36  }
    37  
    38  type ReadMessageBatchOptions struct {
    39  	batcherGetOptions
    40  }
    41  
    42  func (o ReadMessageBatchOptions) clone() ReadMessageBatchOptions {
    43  	return o
    44  }
    45  
    46  func newReadMessageBatchOptions() ReadMessageBatchOptions {
    47  	return ReadMessageBatchOptions{}
    48  }
    49  
    50  // PublicReadBatchOption для различных пожеланий к батчу вроде WithMaxMessages(int)
    51  type PublicReadBatchOption interface {
    52  	Apply(options ReadMessageBatchOptions) ReadMessageBatchOptions
    53  }
    54  
    55  type readExplicitMessagesCount int
    56  
    57  // Apply implements PublicReadBatchOption
    58  func (count readExplicitMessagesCount) Apply(options ReadMessageBatchOptions) ReadMessageBatchOptions {
    59  	options.MinCount = int(count)
    60  	options.MaxCount = int(count)
    61  
    62  	return options
    63  }
    64  
    65  func NewReader(
    66  	client TopicClient,
    67  	connector TopicSteamReaderConnect,
    68  	consumer string,
    69  	readSelectors []topicreadercommon.PublicReadSelector,
    70  	opts ...PublicReaderOption,
    71  ) (Reader, error) {
    72  	cfg := convertNewParamsToStreamConfig(consumer, readSelectors, opts...)
    73  
    74  	if errs := cfg.Validate(); len(errs) > 0 {
    75  		return Reader{}, xerrors.WithStackTrace(fmt.Errorf(
    76  			"ydb: failed to start topic reader, because is contains error in config: %w",
    77  			errors.Join(errs...),
    78  		))
    79  	}
    80  
    81  	readerID := topicreadercommon.NextReaderID()
    82  
    83  	readerConnector := func(ctx context.Context) (batchedStreamReader, error) {
    84  		stream, err := connector(ctx)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  
    89  		return newTopicStreamReader(client, readerID, stream, cfg.topicStreamReaderConfig)
    90  	}
    91  
    92  	res := Reader{
    93  		reader: newReaderReconnector(
    94  			readerID,
    95  			readerConnector,
    96  			cfg.OperationTimeout(),
    97  			cfg.RetrySettings,
    98  			cfg.Trace,
    99  		),
   100  		defaultBatchConfig: cfg.DefaultBatchConfig,
   101  		tracer:             cfg.Trace,
   102  		readerID:           readerID,
   103  	}
   104  
   105  	return res, nil
   106  }
   107  
   108  func (r *Reader) WaitInit(ctx context.Context) error {
   109  	return r.reader.WaitInit(ctx)
   110  }
   111  
   112  func (r *Reader) ID() int64 {
   113  	return r.readerID
   114  }
   115  
   116  func (r *Reader) Tracer() *trace.Topic {
   117  	return r.tracer
   118  }
   119  
   120  func (r *Reader) Close(ctx context.Context) error {
   121  	return r.reader.CloseWithError(ctx, xerrors.WithStackTrace(errReaderClosed))
   122  }
   123  
   124  func (r *Reader) PopBatchTx(
   125  	ctx context.Context,
   126  	tx tx.Transaction,
   127  	opts ...PublicReadBatchOption,
   128  ) (*topicreadercommon.PublicBatch, error) {
   129  	batchOptions := r.getBatchOptions(opts)
   130  
   131  	return r.reader.PopMessagesBatchTx(ctx, tx, batchOptions)
   132  }
   133  
   134  // ReadMessage read exactly one message
   135  func (r *Reader) ReadMessage(ctx context.Context) (*topicreadercommon.PublicMessage, error) {
   136  	res, err := r.ReadMessageBatch(ctx, readExplicitMessagesCount(1))
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	return res.Messages[0], nil
   142  }
   143  
   144  // ReadMessageBatch read batch of messages.
   145  // Batch is collection of messages, which can be atomically committed
   146  func (r *Reader) ReadMessageBatch(
   147  	ctx context.Context,
   148  	opts ...PublicReadBatchOption,
   149  ) (
   150  	batch *topicreadercommon.PublicBatch,
   151  	err error,
   152  ) {
   153  	batchOptions := r.getBatchOptions(opts)
   154  
   155  	for {
   156  		if err = ctx.Err(); err != nil {
   157  			return nil, err
   158  		}
   159  
   160  		batch, err = r.reader.ReadMessageBatch(ctx, batchOptions)
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  
   165  		// if batch context is canceled - do not return it to client
   166  		// and read next batch
   167  		if batch.Context().Err() == nil {
   168  			return batch, nil
   169  		}
   170  	}
   171  }
   172  
   173  func (r *Reader) getBatchOptions(opts []PublicReadBatchOption) ReadMessageBatchOptions {
   174  	readOptions := r.defaultBatchConfig.clone()
   175  
   176  	for _, opt := range opts {
   177  		if opt != nil {
   178  			readOptions = opt.Apply(readOptions)
   179  		}
   180  	}
   181  
   182  	return readOptions
   183  }
   184  
   185  func (r *Reader) Commit(ctx context.Context, offsets topicreadercommon.PublicCommitRangeGetter) (err error) {
   186  	cr := topicreadercommon.GetCommitRange(offsets)
   187  	if cr.PartitionSession.ReaderID != r.readerID {
   188  		return xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf(
   189  			"ydb: messages session reader id (%v) != current reader id (%v): %w",
   190  			cr.PartitionSession.ReaderID, r.readerID, errCommitSessionFromOtherReader,
   191  		)))
   192  	}
   193  
   194  	return r.reader.Commit(ctx, cr)
   195  }
   196  
   197  func (r *Reader) CommitRanges(ctx context.Context, ranges []topicreadercommon.PublicCommitRange) error {
   198  	for i := range ranges {
   199  		commitRange := topicreadercommon.GetCommitRange(ranges[i])
   200  		if commitRange.PartitionSession.ReaderID != r.readerID {
   201  			return xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf(
   202  				"ydb: commit ranges (range item %v) "+
   203  					"messages session reader id (%v) != current reader id (%v): %w",
   204  				i, commitRange.PartitionSession.ReaderID, r.readerID, errCommitSessionFromOtherReader,
   205  			)))
   206  		}
   207  	}
   208  
   209  	commitRanges := topicreadercommon.NewCommitRangesFromPublicCommits(ranges)
   210  	commitRanges.Optimize()
   211  
   212  	commitErrors := make(chan error, commitRanges.Len())
   213  
   214  	var wg sync.WaitGroup
   215  
   216  	commit := func(cr topicreadercommon.CommitRange) {
   217  		defer wg.Done()
   218  		commitErrors <- r.Commit(ctx, &cr)
   219  	}
   220  
   221  	wg.Add(commitRanges.Len())
   222  	for _, cr := range commitRanges.Ranges {
   223  		go commit(cr)
   224  	}
   225  	wg.Wait()
   226  	close(commitErrors)
   227  
   228  	// return first error
   229  	for err := range commitErrors {
   230  		if err != nil {
   231  			return err
   232  		}
   233  	}
   234  
   235  	return nil
   236  }
   237  
   238  type ReaderConfig struct {
   239  	config.Common
   240  
   241  	RetrySettings      topic.RetrySettings
   242  	DefaultBatchConfig ReadMessageBatchOptions
   243  	topicStreamReaderConfig
   244  }
   245  
   246  type PublicReaderOption func(cfg *ReaderConfig)
   247  
   248  func WithCredentials(cred credentials.Credentials) PublicReaderOption {
   249  	return func(cfg *ReaderConfig) {
   250  		if cred == nil {
   251  			cred = credentials.NewAnonymousCredentials()
   252  		}
   253  		cfg.Cred = cred
   254  	}
   255  }
   256  
   257  func WithTrace(tracer *trace.Topic) PublicReaderOption {
   258  	return func(cfg *ReaderConfig) {
   259  		cfg.Trace = cfg.Trace.Compose(tracer)
   260  	}
   261  }
   262  
   263  func convertNewParamsToStreamConfig(
   264  	consumer string,
   265  	readSelectors []topicreadercommon.PublicReadSelector,
   266  	opts ...PublicReaderOption,
   267  ) (cfg ReaderConfig) {
   268  	cfg.topicStreamReaderConfig = newTopicStreamReaderConfig()
   269  	cfg.Consumer = consumer
   270  
   271  	// make own copy, for prevent changing internal states if readSelectors will change outside
   272  	cfg.ReadSelectors = make([]*topicreadercommon.PublicReadSelector, len(readSelectors))
   273  	for i := range readSelectors {
   274  		cfg.ReadSelectors[i] = readSelectors[i].Clone()
   275  	}
   276  
   277  	for _, opt := range opts {
   278  		if opt != nil {
   279  			opt(&cfg)
   280  		}
   281  	}
   282  
   283  	return cfg
   284  }