github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/topic/topicreader/reader.go (about) 1 package topicreader 2 3 import ( 4 "context" 5 "sync/atomic" 6 7 "github.com/ydb-platform/ydb-go-sdk/v3/internal/topic/topicreadercommon" 8 "github.com/ydb-platform/ydb-go-sdk/v3/internal/topic/topicreaderinternal" 9 "github.com/ydb-platform/ydb-go-sdk/v3/internal/tx" 10 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 11 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 12 ) 13 14 // Reader allow to read message from YDB topics. 15 // ReadMessage or ReadMessageBatch can call concurrency with Commit, other concurrency call is denied. 16 // 17 // In other words you can have one goroutine for read messages and one goroutine for commit messages. 18 // 19 // Concurrency table 20 // | Method | ReadMessage | ReadMessageBatch | Commit | Close | 21 // | ReadMessage | - | - | + | - | 22 // | ReadMessageBatch | - | - | + | - | 23 // | Commit | + | + | - | - | 24 // | Close | - | - | - | - | 25 type Reader struct { 26 reader topicreaderinternal.Reader 27 readInFlyght atomic.Bool 28 commitInFlyght atomic.Bool 29 } 30 31 // NewReader 32 // create new reader, used internally only. 33 func NewReader(internalReader topicreaderinternal.Reader) *Reader { 34 return &Reader{reader: internalReader} 35 } 36 37 // WaitInit waits until the reader is initialized 38 // or an error occurs 39 func (r *Reader) WaitInit(ctx context.Context) error { 40 return r.reader.WaitInit(ctx) 41 } 42 43 // ReadMessage read exactly one message 44 // exactly one of message, error is nil 45 func (r *Reader) ReadMessage(ctx context.Context) (*Message, error) { 46 if err := r.inCall(&r.readInFlyght); err != nil { 47 return nil, err 48 } 49 defer r.outCall(&r.readInFlyght) 50 51 return r.reader.ReadMessage(ctx) 52 } 53 54 // Message contains data and metadata, readed from the server 55 type Message = topicreadercommon.PublicMessage 56 57 // MessageContentUnmarshaler is interface for unmarshal message content to own struct 58 type MessageContentUnmarshaler = topicreadercommon.PublicMessageContentUnmarshaler 59 60 // Commit receive Message, Batch of single offset 61 // It can be fast (by default) or sync and waite response from server 62 // see topicoptions.CommitMode for details 63 // 64 // for topicoptions.CommitModeSync mode sync the method can return ErrCommitToExpiredSession 65 // it means about the message/batch was not committed because connection broken or partition routed to 66 // other reader by server. 67 // Client code should continue work normally 68 func (r *Reader) Commit(ctx context.Context, obj CommitRangeGetter) error { 69 if err := r.inCall(&r.commitInFlyght); err != nil { 70 return err 71 } 72 defer r.outCall(&r.commitInFlyght) 73 74 return r.reader.Commit(ctx, obj) 75 } 76 77 // PopMessagesBatchTx read messages batch and commit them within tx. 78 // If tx failed - the batch will be received again. 79 // 80 // Now it means reconnect to the server and re-read messages from the server to the readers buffer. 81 // It is expensive operation and will be good to minimize transaction failures. 82 // 83 // The reconnect is implementation detail and may be changed in the future. 84 // 85 // Experimental: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#experimental 86 func (r *Reader) PopMessagesBatchTx( 87 ctx context.Context, 88 transaction tx.Identifier, 89 opts ...ReadBatchOption, 90 ) ( 91 resBatch *Batch, 92 resErr error, 93 ) { 94 if err := r.inCall(&r.readInFlyght); err != nil { 95 return nil, err 96 } 97 defer r.outCall(&r.readInFlyght) 98 99 internalTx, err := tx.AsTransaction(transaction) 100 if err != nil { 101 return nil, err 102 } 103 104 tracer := r.reader.Tracer() 105 106 traceCtx := ctx 107 onDone := trace.TopicOnReaderPopBatchTx(tracer, &traceCtx, r.reader.ID(), internalTx.SessionID(), internalTx) 108 ctx = traceCtx 109 110 defer func() { 111 var startOffset, endOffset int64 112 var messagesCount int 113 114 if resBatch != nil { 115 messagesCount = len(resBatch.Messages) 116 commitRange := topicreadercommon.GetCommitRange(resBatch) 117 startOffset = commitRange.CommitOffsetStart.ToInt64() 118 endOffset = commitRange.CommitOffsetEnd.ToInt64() 119 } 120 onDone(startOffset, endOffset, messagesCount, resErr) 121 }() 122 123 return r.reader.PopBatchTx(ctx, internalTx, opts...) 124 } 125 126 // CommitRangeGetter interface for get commit offsets 127 type CommitRangeGetter = topicreadercommon.PublicCommitRangeGetter 128 129 // ReadMessageBatch 130 // 131 // Deprecated: was experimental and not actual now. 132 // Use ReadMessagesBatch instead. 133 // Will be removed after Oct 2024. 134 // Read about versioning policy: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#deprecated 135 func (r *Reader) ReadMessageBatch(ctx context.Context, opts ...ReadBatchOption) (*Batch, error) { 136 if err := r.inCall(&r.readInFlyght); err != nil { 137 return nil, err 138 } 139 defer r.outCall(&r.readInFlyght) 140 141 return r.reader.ReadMessageBatch(ctx, opts...) 142 } 143 144 // ReadMessagesBatch read batch of messages 145 // Batch is ordered message group from one partition 146 // exactly one of Batch, err is nil 147 // if Batch is not nil - reader guarantee about all Batch.Messages are not nil 148 func (r *Reader) ReadMessagesBatch(ctx context.Context, opts ...ReadBatchOption) (*Batch, error) { 149 if err := r.inCall(&r.readInFlyght); err != nil { 150 return nil, err 151 } 152 defer r.outCall(&r.readInFlyght) 153 154 return r.reader.ReadMessageBatch(ctx, opts...) 155 } 156 157 // Batch is ordered group of messages from one partition 158 type Batch = topicreadercommon.PublicBatch 159 160 // ReadBatchOption is type for options of read batch 161 type ReadBatchOption = topicreaderinternal.PublicReadBatchOption 162 163 // Close stop work with reader 164 // return when reader complete internal works, flush commit buffer, ets 165 // or when ctx cancelled 166 func (r *Reader) Close(ctx context.Context) error { 167 // close must be non-concurrent with read and commit 168 169 if err := r.inCall(&r.readInFlyght); err != nil { 170 return err 171 } 172 defer r.outCall(&r.readInFlyght) 173 174 if err := r.inCall(&r.commitInFlyght); err != nil { 175 return err 176 } 177 defer r.outCall(&r.commitInFlyght) 178 179 return r.reader.Close(ctx) 180 } 181 182 func (r *Reader) inCall(inFlight *atomic.Bool) error { 183 if inFlight.CompareAndSwap(false, true) { 184 return nil 185 } 186 187 return xerrors.WithStackTrace(ErrConcurrencyCall) 188 } 189 190 func (r *Reader) outCall(inFlight *atomic.Bool) { 191 if inFlight.CompareAndSwap(true, false) { 192 return 193 } 194 195 panic("ydb: topic reader out call without in call, must be never") 196 }