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 }