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

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