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 }