github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/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/topicreaderinternal"
     8  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
     9  )
    10  
    11  // Reader allow to read message from YDB topics.
    12  // ReadMessage or ReadMessageBatch can call concurrency with Commit, other concurrency call is denied.
    13  //
    14  // In other words you can have one goroutine for read messages and one goroutine for commit messages.
    15  //
    16  // Concurrency table
    17  // | Method           | ReadMessage | ReadMessageBatch | Commit | Close |
    18  // | ReadMessage      |      -      |         -        |   +    | -     |
    19  // | ReadMessageBatch |      -      |         -        |   +    | -     |
    20  // | Commit           |      +      |         +        |   -    | -     |
    21  // | Close            |      -      |         -        |   -    | -     |
    22  type Reader struct {
    23  	reader         topicreaderinternal.Reader
    24  	readInFlyght   atomic.Bool
    25  	commitInFlyght atomic.Bool
    26  }
    27  
    28  // NewReader
    29  // create new reader, used internally only.
    30  func NewReader(internalReader topicreaderinternal.Reader) *Reader {
    31  	return &Reader{reader: internalReader}
    32  }
    33  
    34  // WaitInit waits until the reader is initialized
    35  // or an error occurs
    36  func (r *Reader) WaitInit(ctx context.Context) error {
    37  	return r.reader.WaitInit(ctx)
    38  }
    39  
    40  // ReadMessage read exactly one message
    41  // exactly one of message, error is nil
    42  func (r *Reader) ReadMessage(ctx context.Context) (*Message, error) {
    43  	if err := r.inCall(&r.readInFlyght); err != nil {
    44  		return nil, err
    45  	}
    46  	defer r.outCall(&r.readInFlyght)
    47  
    48  	return r.reader.ReadMessage(ctx)
    49  }
    50  
    51  // Message contains data and metadata, readed from the server
    52  type Message = topicreaderinternal.PublicMessage
    53  
    54  // MessageContentUnmarshaler is interface for unmarshal message content to own struct
    55  type MessageContentUnmarshaler = topicreaderinternal.PublicMessageContentUnmarshaler
    56  
    57  // Commit receive Message, Batch of single offset
    58  // It can be fast (by default) or sync and waite response from server
    59  // see topicoptions.CommitMode for details
    60  //
    61  // for topicoptions.CommitModeSync mode sync the method can return ErrCommitToExpiredSession
    62  // it means about the message/batch was not committed because connection broken or partition routed to
    63  // other reader by server.
    64  // Client code should continue work normally
    65  func (r *Reader) Commit(ctx context.Context, obj CommitRangeGetter) error {
    66  	if err := r.inCall(&r.commitInFlyght); err != nil {
    67  		return err
    68  	}
    69  	defer r.outCall(&r.commitInFlyght)
    70  
    71  	return r.reader.Commit(ctx, obj)
    72  }
    73  
    74  // CommitRangeGetter interface for get commit offsets
    75  type CommitRangeGetter = topicreaderinternal.PublicCommitRangeGetter
    76  
    77  // ReadMessageBatch
    78  // Deprecated: (was experimental) will be removed soon.
    79  // Use ReadMessagesBatch instead.
    80  func (r *Reader) ReadMessageBatch(ctx context.Context, opts ...ReadBatchOption) (*Batch, error) {
    81  	if err := r.inCall(&r.readInFlyght); err != nil {
    82  		return nil, err
    83  	}
    84  	defer r.outCall(&r.readInFlyght)
    85  
    86  	return r.reader.ReadMessageBatch(ctx, opts...)
    87  }
    88  
    89  // ReadMessagesBatch read batch of messages
    90  // Batch is ordered message group from one partition
    91  // exactly one of Batch, err is nil
    92  // if Batch is not nil - reader guarantee about all Batch.Messages are not nil
    93  func (r *Reader) ReadMessagesBatch(ctx context.Context, opts ...ReadBatchOption) (*Batch, error) {
    94  	if err := r.inCall(&r.readInFlyght); err != nil {
    95  		return nil, err
    96  	}
    97  	defer r.outCall(&r.readInFlyght)
    98  
    99  	return r.reader.ReadMessageBatch(ctx, opts...)
   100  }
   101  
   102  // Batch is ordered group of messages from one partition
   103  type Batch = topicreaderinternal.PublicBatch
   104  
   105  // ReadBatchOption is type for options of read batch
   106  type ReadBatchOption = topicreaderinternal.PublicReadBatchOption
   107  
   108  // Close stop work with reader
   109  // return when reader complete internal works, flush commit buffer, ets
   110  // or when ctx cancelled
   111  func (r *Reader) Close(ctx context.Context) error {
   112  	// close must be non-concurrent with read and commit
   113  
   114  	if err := r.inCall(&r.readInFlyght); err != nil {
   115  		return err
   116  	}
   117  	defer r.outCall(&r.readInFlyght)
   118  
   119  	if err := r.inCall(&r.commitInFlyght); err != nil {
   120  		return err
   121  	}
   122  	defer r.outCall(&r.commitInFlyght)
   123  
   124  	return r.reader.Close(ctx)
   125  }
   126  
   127  func (r *Reader) inCall(inFlight *atomic.Bool) error {
   128  	if inFlight.CompareAndSwap(false, true) {
   129  		return nil
   130  	}
   131  
   132  	return xerrors.WithStackTrace(ErrConcurrencyCall)
   133  }
   134  
   135  func (r *Reader) outCall(inFlight *atomic.Bool) {
   136  	if inFlight.CompareAndSwap(true, false) {
   137  		return
   138  	}
   139  
   140  	panic("ydb: topic reader out call without in call, must be never")
   141  }