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 }