github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/topic/topicwriterinternal/writer_single_stream.go (about)

     1  package topicwriterinternal
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"sync/atomic"
     9  
    10  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/background"
    11  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/empty"
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopiccommon"
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicwriter"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    17  )
    18  
    19  var errSingleStreamWriterDoubleClose = xerrors.Wrap(errors.New("ydb: single stream writer impl double closed"))
    20  
    21  type SingleStreamWriterConfig struct {
    22  	WritersCommonConfig
    23  
    24  	stream                RawTopicWriterStream
    25  	queue                 *messageQueue
    26  	encodersMap           *EncoderMap
    27  	getLastSeqNum         bool
    28  	reconnectorInstanceID string
    29  }
    30  
    31  func newSingleStreamWriterConfig(
    32  	common WritersCommonConfig, //nolint:gocritic
    33  	stream RawTopicWriterStream,
    34  	queue *messageQueue,
    35  	encodersMap *EncoderMap,
    36  	getLastSeqNum bool,
    37  	reconnectorID string,
    38  ) SingleStreamWriterConfig {
    39  	return SingleStreamWriterConfig{
    40  		WritersCommonConfig:   common,
    41  		stream:                stream,
    42  		queue:                 queue,
    43  		encodersMap:           encodersMap,
    44  		getLastSeqNum:         getLastSeqNum,
    45  		reconnectorInstanceID: reconnectorID,
    46  	}
    47  }
    48  
    49  type SingleStreamWriter struct {
    50  	cfg                 SingleStreamWriterConfig
    51  	Encoder             EncoderSelector
    52  	background          background.Worker
    53  	CodecsFromServer    rawtopiccommon.SupportedCodecs
    54  	allowedCodecs       rawtopiccommon.SupportedCodecs
    55  	SessionID           string
    56  	closeReason         error
    57  	ReceivedLastSeqNum  int64
    58  	PartitionID         int64
    59  	closeCompleted      empty.Chan
    60  	closed              atomic.Bool
    61  	LastSeqNumRequested bool
    62  }
    63  
    64  func NewSingleStreamWriter(
    65  	ctxForPProfLabelsOnly context.Context,
    66  	cfg SingleStreamWriterConfig, //nolint:gocritic
    67  ) (*SingleStreamWriter, error) {
    68  	res := newSingleStreamWriterStopped(ctxForPProfLabelsOnly, cfg)
    69  	err := res.initStream()
    70  	if err != nil {
    71  		_ = res.close(context.Background(), err)
    72  
    73  		return nil, err
    74  	}
    75  	res.start()
    76  
    77  	return res, nil
    78  }
    79  
    80  func newSingleStreamWriterStopped(
    81  	ctxForPProfLabelsOnly context.Context,
    82  	cfg SingleStreamWriterConfig, //nolint:gocritic
    83  ) *SingleStreamWriter {
    84  	return &SingleStreamWriter{
    85  		cfg:            cfg,
    86  		background:     *background.NewWorker(xcontext.WithoutDeadline(ctxForPProfLabelsOnly)),
    87  		closeCompleted: make(empty.Chan),
    88  	}
    89  }
    90  
    91  func (w *SingleStreamWriter) close(ctx context.Context, reason error) error {
    92  	if !w.closed.CompareAndSwap(false, true) {
    93  		return xerrors.WithStackTrace(errSingleStreamWriterDoubleClose)
    94  	}
    95  
    96  	defer close(w.closeCompleted)
    97  	w.closeReason = reason
    98  
    99  	resErr := w.cfg.stream.CloseSend()
   100  	bgWaitErr := w.background.Close(ctx, reason)
   101  	if resErr == nil {
   102  		resErr = bgWaitErr
   103  	}
   104  
   105  	return resErr
   106  }
   107  
   108  func (w *SingleStreamWriter) WaitClose(ctx context.Context) error {
   109  	select {
   110  	case <-ctx.Done():
   111  		return ctx.Err()
   112  	case <-w.closeCompleted:
   113  		return w.closeReason
   114  	}
   115  }
   116  
   117  func (w *SingleStreamWriter) start() {
   118  	w.background.Start("topic writer update token", w.updateTokenLoop)
   119  	w.background.Start("topic writer send messages", w.sendMessagesFromQueueToStreamLoop)
   120  	w.background.Start("topic writer receive messages", w.receiveMessagesLoop)
   121  }
   122  
   123  func (w *SingleStreamWriter) initStream() (err error) {
   124  	traceOnDone := trace.TopicOnWriterInitStream(w.cfg.tracer, w.cfg.reconnectorInstanceID, w.cfg.topic, w.cfg.producerID)
   125  	defer traceOnDone(w.SessionID, err)
   126  
   127  	req := w.createInitRequest()
   128  	if err = w.cfg.stream.Send(&req); err != nil {
   129  		return err
   130  	}
   131  	recvMessage, err := w.cfg.stream.Recv()
   132  	if err != nil {
   133  		return err
   134  	}
   135  	result, ok := recvMessage.(*rawtopicwriter.InitResult)
   136  	if !ok {
   137  		return xerrors.WithStackTrace(
   138  			fmt.Errorf("ydb: failed init response message type: %v", reflect.TypeOf(recvMessage)),
   139  		)
   140  	}
   141  
   142  	w.allowedCodecs = calculateAllowedCodecs(w.cfg.forceCodec, w.cfg.encodersMap, result.SupportedCodecs)
   143  	if len(w.allowedCodecs) == 0 {
   144  		return xerrors.WithStackTrace(errNoAllowedCodecs)
   145  	}
   146  
   147  	w.Encoder = NewEncoderSelector(
   148  		w.cfg.encodersMap,
   149  		w.allowedCodecs,
   150  		w.cfg.compressorCount,
   151  		w.cfg.tracer,
   152  		w.cfg.reconnectorInstanceID,
   153  		w.SessionID,
   154  	)
   155  
   156  	w.SessionID = result.SessionID
   157  	w.LastSeqNumRequested = req.GetLastSeqNo
   158  	w.ReceivedLastSeqNum = result.LastSeqNo
   159  	w.PartitionID = result.PartitionID
   160  	w.CodecsFromServer = result.SupportedCodecs
   161  
   162  	return nil
   163  }
   164  
   165  func (w *SingleStreamWriter) createInitRequest() rawtopicwriter.InitRequest {
   166  	return rawtopicwriter.InitRequest{
   167  		Path:             w.cfg.topic,
   168  		ProducerID:       w.cfg.producerID,
   169  		WriteSessionMeta: w.cfg.writerMeta,
   170  		Partitioning:     w.cfg.defaultPartitioning,
   171  		GetLastSeqNo:     w.cfg.getLastSeqNum,
   172  	}
   173  }
   174  
   175  func (w *SingleStreamWriter) receiveMessagesLoop(ctx context.Context) {
   176  	for {
   177  		if ctx.Err() != nil {
   178  			return
   179  		}
   180  
   181  		mess, err := w.cfg.stream.Recv()
   182  		if err != nil {
   183  			err = xerrors.WithStackTrace(fmt.Errorf("ydb: failed to receive message from write stream: %w", err))
   184  			_ = w.close(ctx, err)
   185  
   186  			return
   187  		}
   188  
   189  		switch m := mess.(type) {
   190  		case *rawtopicwriter.WriteResult:
   191  			if err = w.cfg.queue.AcksReceived(m.Acks); err != nil && !errors.Is(err, errCloseClosedMessageQueue) {
   192  				reason := xerrors.WithStackTrace(err)
   193  				closeCtx, closeCtxCancel := xcontext.WithCancel(ctx)
   194  				closeCtxCancel()
   195  				_ = w.close(closeCtx, reason)
   196  
   197  				return
   198  			}
   199  		case *rawtopicwriter.UpdateTokenResponse:
   200  			// pass
   201  		default:
   202  			trace.TopicOnWriterReadUnknownGrpcMessage(
   203  				w.cfg.tracer,
   204  				w.cfg.reconnectorInstanceID,
   205  				w.SessionID,
   206  				xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf(
   207  					"ydb: unexpected message type in stream reader: %v",
   208  					reflect.TypeOf(m),
   209  				))),
   210  			)
   211  		}
   212  	}
   213  }
   214  
   215  func (w *SingleStreamWriter) sendMessagesFromQueueToStreamLoop(ctx context.Context) {
   216  	for {
   217  		messages, err := w.cfg.queue.GetMessagesForSend(ctx)
   218  		if err != nil {
   219  			_ = w.close(ctx, err)
   220  
   221  			return
   222  		}
   223  
   224  		targetCodec, err := w.Encoder.CompressMessages(messages)
   225  		if err != nil {
   226  			_ = w.close(ctx, err)
   227  
   228  			return
   229  		}
   230  
   231  		onSentComplete := trace.TopicOnWriterSendMessages(
   232  			w.cfg.tracer,
   233  			w.cfg.reconnectorInstanceID,
   234  			w.SessionID,
   235  			targetCodec.ToInt32(),
   236  			messages[0].SeqNo,
   237  			len(messages),
   238  		)
   239  		err = sendMessagesToStream(w.cfg.stream, targetCodec, messages)
   240  		onSentComplete(err)
   241  		if err != nil {
   242  			err = xerrors.WithStackTrace(fmt.Errorf("ydb: error send message to topic stream: %w", err))
   243  			_ = w.close(ctx, err)
   244  
   245  			return
   246  		}
   247  	}
   248  }
   249  
   250  func (w *SingleStreamWriter) updateTokenLoop(ctx context.Context) {
   251  	if ctx.Err() != nil {
   252  		return
   253  	}
   254  
   255  	ticker := w.cfg.clock.NewTicker(w.cfg.credUpdateInterval)
   256  	defer ticker.Stop()
   257  
   258  	ctxDone := ctx.Done()
   259  	tickerChan := ticker.Chan()
   260  	for {
   261  		select {
   262  		case <-ctxDone:
   263  			return
   264  		case <-tickerChan:
   265  			_ = w.sendUpdateToken(ctx)
   266  		}
   267  	}
   268  }
   269  
   270  func (w *SingleStreamWriter) sendUpdateToken(ctx context.Context) (err error) {
   271  	token, err := w.cfg.cred.Token(ctx)
   272  	if err != nil {
   273  		return err
   274  	}
   275  
   276  	stream := w.cfg.stream
   277  	if stream == nil {
   278  		// not connected yet
   279  		return nil
   280  	}
   281  
   282  	req := &rawtopicwriter.UpdateTokenRequest{}
   283  	req.Token = token
   284  
   285  	return stream.Send(req)
   286  }