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  }