github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/topic/topicwriterinternal/writer_reconnector.go (about) 1 package topicwriterinternal 2 3 import ( 4 "context" 5 "crypto/rand" 6 "errors" 7 "fmt" 8 "math" 9 "math/big" 10 "runtime" 11 "sync/atomic" 12 "time" 13 14 "github.com/google/uuid" 15 "github.com/jonboulle/clockwork" 16 "golang.org/x/sync/semaphore" 17 18 "github.com/ydb-platform/ydb-go-sdk/v3/credentials" 19 "github.com/ydb-platform/ydb-go-sdk/v3/internal/background" 20 "github.com/ydb-platform/ydb-go-sdk/v3/internal/config" 21 "github.com/ydb-platform/ydb-go-sdk/v3/internal/empty" 22 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopiccommon" 23 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicwriter" 24 "github.com/ydb-platform/ydb-go-sdk/v3/internal/topic" 25 "github.com/ydb-platform/ydb-go-sdk/v3/internal/value" 26 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 27 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 28 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" 29 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topictypes" 30 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 31 ) 32 33 var ( 34 errConnTimeout = xerrors.Wrap(errors.New("ydb: connection timeout")) 35 errStopWriterReconnector = xerrors.Wrap(errors.New("ydb: stop writer reconnector")) 36 errNonZeroSeqNo = xerrors.Wrap(errors.New("ydb: non zero seqno for auto set seqno mode")) //nolint:lll 37 errNonZeroCreatedAt = xerrors.Wrap(errors.New("ydb: non zero Message.CreatedAt and set auto fill created at option")) //nolint:lll 38 errNoAllowedCodecs = xerrors.Wrap(errors.New("ydb: no allowed codecs for write to topic")) 39 errLargeMessage = xerrors.Wrap(errors.New("ydb: message uncompressed size more, then limit")) //nolint:lll 40 PublicErrQueueIsFull = xerrors.Wrap(errors.New("ydb: queue is full")) 41 PublicErrMessagesPutToInternalQueueBeforeError = xerrors.Wrap(errors.New("ydb: the messages was put to internal buffer before the error happened. It mean about the messages can be delivered to the server")) //nolint:lll 42 errDiffetentTransactions = xerrors.Wrap(errors.New("ydb: internal writer has messages from different trasactions. It is internal logic error, write issue please: https://github.com/ydb-platform/ydb-go-sdk/issues/new?assignees=&labels=bug&projects=&template=01_BUG_REPORT.md&title=bug%3A+")) //nolint:lll 43 44 // errProducerIDNotEqualMessageGroupID is temporary 45 // WithMessageGroupID is optional parameter because it allowed to be skipped by protocol. 46 // But right not YDB server doesn't implement it. 47 // It is fast check for return error at writer create context instead of stream initialization 48 // The error will remove in the future, when skip message group id will be allowed by server. 49 errProducerIDNotEqualMessageGroupID = xerrors.Wrap(errors.New("ydb: producer id not equal to message group id, use option WithMessageGroupID(producerID) for create writer")) //nolint:lll 50 ) 51 52 type WriterReconnectorConfig struct { 53 WritersCommonConfig 54 55 MaxMessageSize int 56 MaxQueueLen int 57 Common config.Common 58 AdditionalEncoders map[rawtopiccommon.Codec]PublicCreateEncoderFunc 59 Connect ConnectFunc 60 WaitServerAck bool 61 AutoSetSeqNo bool 62 AutoSetCreatedTime bool 63 OnWriterInitResponseCallback PublicOnWriterInitResponseCallback 64 RetrySettings topic.RetrySettings 65 66 connectTimeout time.Duration 67 } 68 69 func (cfg *WriterReconnectorConfig) validate() error { 70 if cfg.defaultPartitioning.Type == rawtopicwriter.PartitioningMessageGroupID && 71 cfg.producerID != cfg.defaultPartitioning.MessageGroupID { 72 return xerrors.WithStackTrace(errProducerIDNotEqualMessageGroupID) 73 } 74 75 return nil 76 } 77 78 func NewWriterReconnectorConfig(options ...PublicWriterOption) WriterReconnectorConfig { 79 cfg := WriterReconnectorConfig{ 80 WritersCommonConfig: WritersCommonConfig{ 81 cred: credentials.NewAnonymousCredentials(), 82 credUpdateInterval: time.Hour, 83 clock: clockwork.NewRealClock(), 84 compressorCount: runtime.NumCPU(), 85 Tracer: &trace.Topic{}, 86 }, 87 AutoSetSeqNo: true, 88 AutoSetCreatedTime: true, 89 MaxMessageSize: 50 * 1024 * 1024, //nolint:gomnd 90 MaxQueueLen: 1000, //nolint:gomnd 91 RetrySettings: topic.RetrySettings{ 92 StartTimeout: topic.DefaultStartTimeout, 93 }, 94 } 95 if cfg.compressorCount == 0 { 96 cfg.compressorCount = 1 97 } 98 99 for _, f := range options { 100 f(&cfg) 101 } 102 103 if cfg.connectTimeout == 0 { 104 cfg.connectTimeout = cfg.Common.OperationTimeout() 105 } 106 107 if cfg.connectTimeout == 0 { 108 cfg.connectTimeout = value.InfiniteDuration 109 } 110 111 if cfg.producerID == "" { 112 WithProducerID(uuid.NewString())(&cfg) 113 } 114 115 return cfg 116 } 117 118 type WriterReconnector struct { 119 cfg WriterReconnectorConfig 120 queue messageQueue 121 background background.Worker 122 retrySettings topic.RetrySettings 123 writerInstanceID string 124 semaphore *semaphore.Weighted 125 firstInitResponseProcessedChan empty.Chan 126 lastSeqNo int64 127 encodersMap *EncoderMap 128 initDoneCh empty.Chan 129 initInfo InitialInfo 130 m xsync.RWMutex 131 sessionID string 132 firstConnectionHandled atomic.Bool 133 initDone bool 134 } 135 136 func NewWriterReconnector( 137 cfg WriterReconnectorConfig, 138 ) (*WriterReconnector, error) { 139 if err := cfg.validate(); err != nil { 140 return nil, err 141 } 142 143 res := newWriterReconnectorStopped(cfg) 144 res.start() 145 146 return res, nil 147 } 148 149 func newWriterReconnectorStopped( 150 cfg WriterReconnectorConfig, //nolint:gocritic 151 ) *WriterReconnector { 152 writerInstanceID, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) 153 res := &WriterReconnector{ 154 cfg: cfg, 155 semaphore: semaphore.NewWeighted(int64(cfg.MaxQueueLen)), 156 queue: newMessageQueue(), 157 lastSeqNo: -1, 158 firstInitResponseProcessedChan: make(empty.Chan), 159 encodersMap: NewEncoderMap(), 160 writerInstanceID: writerInstanceID.String(), 161 retrySettings: cfg.RetrySettings, 162 } 163 164 res.queue.OnAckReceived = res.onAckReceived 165 166 for codec, creator := range cfg.AdditionalEncoders { 167 res.encodersMap.AddEncoder(codec, creator) 168 } 169 170 res.sessionID = "not-connected-" + writerInstanceID.String() 171 172 res.initDoneCh = make(empty.Chan) 173 174 return res 175 } 176 177 func (w *WriterReconnector) fillFields(messages []messageWithDataContent) error { 178 var now time.Time 179 180 for i := range messages { 181 msg := &messages[i] 182 183 // SetSeqNo 184 if w.cfg.AutoSetSeqNo { 185 if msg.SeqNo != 0 { 186 return xerrors.WithStackTrace(errNonZeroSeqNo) 187 } 188 w.lastSeqNo++ 189 msg.SeqNo = w.lastSeqNo 190 } 191 192 // Set created time 193 if w.cfg.AutoSetCreatedTime { 194 if msg.CreatedAt.IsZero() { 195 if now.IsZero() { 196 now = w.cfg.clock.Now() 197 } 198 msg.CreatedAt = now 199 } else { 200 return xerrors.WithStackTrace(errNonZeroCreatedAt) 201 } 202 } 203 } 204 205 return nil 206 } 207 208 func (w *WriterReconnector) start() { 209 name := fmt.Sprintf("writer %q", w.cfg.topic) 210 w.background.Start(name+", sendloop", w.connectionLoop) 211 } 212 213 func (w *WriterReconnector) Write(ctx context.Context, messages []PublicMessage) (resErr error) { 214 if err := w.background.CloseReason(); err != nil { 215 return xerrors.WithStackTrace(fmt.Errorf("ydb: writer is closed: %w", err)) 216 } 217 if ctx.Err() != nil { 218 return ctx.Err() 219 } 220 if len(messages) == 0 { 221 return nil 222 } 223 224 semaphoreWeight := int64(len(messages)) 225 if semaphoreWeight > int64(w.cfg.MaxQueueLen) { 226 return xerrors.WithStackTrace(fmt.Errorf( 227 "ydb: add more messages, then max queue limit. max queue: %v, try to add: %v: %w", 228 w.cfg.MaxQueueLen, 229 semaphoreWeight, 230 PublicErrQueueIsFull, 231 )) 232 } 233 if err := w.semaphore.Acquire(ctx, semaphoreWeight); err != nil { 234 return xerrors.WithStackTrace( 235 fmt.Errorf("ydb: add new messages exceed max queue size limit. Add count: %v, max size: %v: %w", 236 semaphoreWeight, 237 w.cfg.MaxQueueLen, 238 PublicErrQueueIsFull, 239 )) 240 } 241 defer func() { 242 w.semaphore.Release(semaphoreWeight) 243 }() 244 245 messagesSlice, err := w.createMessagesWithContent(messages) 246 if err != nil { 247 return err 248 } 249 250 if err = w.checkMessages(messagesSlice); err != nil { 251 return err 252 } 253 254 if err = w.waitFirstInitResponse(ctx); err != nil { 255 return err 256 } 257 258 waiter, err := w.addMessageToInternalQueueWithLock(messagesSlice, &semaphoreWeight) 259 if err != nil { 260 return err 261 } 262 defer func() { 263 if resErr != nil { 264 resErr = xerrors.Join(resErr, PublicErrMessagesPutToInternalQueueBeforeError) 265 } 266 }() 267 268 if !w.cfg.WaitServerAck { 269 return nil 270 } 271 272 return w.queue.Wait(ctx, waiter) 273 } 274 275 func (w *WriterReconnector) addMessageToInternalQueueWithLock( 276 messagesSlice []messageWithDataContent, 277 semaphoreWeight *int64, 278 ) (MessageQueueAckWaiter, error) { 279 var ( 280 waiter MessageQueueAckWaiter 281 err error 282 ) 283 w.m.WithLock(func() { 284 // need set numbers and add to queue atomically 285 err = w.fillFields(messagesSlice) 286 if err != nil { 287 return 288 } 289 290 if w.cfg.WaitServerAck { 291 waiter, err = w.queue.AddMessagesWithWaiter(messagesSlice) 292 } else { 293 err = w.queue.AddMessages(messagesSlice) 294 } 295 if err == nil { 296 // move semaphore weight to queue 297 *semaphoreWeight = 0 298 } 299 }) 300 301 return waiter, err 302 } 303 304 func (w *WriterReconnector) checkMessages(messages []messageWithDataContent) error { 305 for i := range messages { 306 size := messages[i].BufUncompressedSize 307 if size > w.cfg.MaxMessageSize { 308 return xerrors.WithStackTrace(fmt.Errorf("message size bytes %v: %w", size, errLargeMessage)) 309 } 310 } 311 312 return nil 313 } 314 315 func (w *WriterReconnector) createMessagesWithContent(messages []PublicMessage) ([]messageWithDataContent, error) { 316 res := make([]messageWithDataContent, 0, len(messages)) 317 for i := range messages { 318 mess := newMessageDataWithContent(messages[i], w.encodersMap) 319 res = append(res, mess) 320 } 321 322 var sessionID string 323 w.m.WithRLock(func() { 324 sessionID = w.sessionID 325 }) 326 onCompressDone := trace.TopicOnWriterCompressMessages( 327 w.cfg.Tracer, 328 w.writerInstanceID, 329 sessionID, 330 w.cfg.forceCodec.ToInt32(), 331 messages[0].SeqNo, 332 len(messages), 333 trace.TopicWriterCompressMessagesReasonCompressDataOnWriteReadData, 334 ) 335 336 targetCodec := w.cfg.forceCodec 337 if targetCodec == rawtopiccommon.CodecUNSPECIFIED { 338 targetCodec = rawtopiccommon.CodecRaw 339 } 340 err := cacheMessages(res, targetCodec, w.cfg.compressorCount) 341 onCompressDone(err) 342 if err != nil { 343 return nil, err 344 } 345 346 return res, nil 347 } 348 349 func (w *WriterReconnector) Flush(ctx context.Context) error { 350 return w.queue.WaitLastWritten(ctx) 351 } 352 353 func (w *WriterReconnector) Close(ctx context.Context) error { 354 reason := xerrors.WithStackTrace(errStopWriterReconnector) 355 w.queue.StopAddNewMessages(reason) 356 357 flushErr := w.Flush(ctx) //nolint:ifshort,nolintlint 358 closeErr := w.close(ctx, reason) 359 360 if flushErr != nil { 361 return flushErr 362 } 363 364 return closeErr 365 } 366 367 func (w *WriterReconnector) close(ctx context.Context, reason error) (resErr error) { 368 onDone := trace.TopicOnWriterClose(w.cfg.Tracer, w.writerInstanceID, reason) 369 defer func() { 370 onDone(resErr) 371 }() 372 373 // stop background work and single stream writer 374 bgErr := w.background.Close(ctx, reason) 375 if resErr == nil && bgErr != nil { 376 resErr = bgErr 377 } 378 379 closeErr := w.queue.Close(reason) 380 if resErr == nil && closeErr != nil { 381 resErr = closeErr 382 } 383 384 return resErr 385 } 386 387 func (w *WriterReconnector) connectionLoop(ctx context.Context) { 388 attempt := 0 389 390 createStreamContext := func() (context.Context, context.CancelFunc) { 391 // need suppress parent context cancelation for flush buffer while close writer 392 return xcontext.WithCancel(xcontext.ValueOnly(ctx)) 393 } 394 395 //nolint:ineffassign,staticcheck,wastedassign 396 streamCtx, streamCtxCancel := createStreamContext() 397 398 defer streamCtxCancel() 399 400 var reconnectReason error 401 var prevAttemptTime time.Time 402 var startOfRetries time.Time 403 404 for { 405 if ctx.Err() != nil { 406 return 407 } 408 409 streamCtxCancel() 410 streamCtx, streamCtxCancel = createStreamContext() 411 412 now := time.Now() 413 if startOfRetries.IsZero() || topic.CheckResetReconnectionCounters(prevAttemptTime, now, w.cfg.connectTimeout) { 414 attempt = 0 415 startOfRetries = w.cfg.clock.Now() 416 } else { 417 attempt++ 418 } 419 prevAttemptTime = now 420 421 if reconnectReason != nil { 422 if w.handleReconnectRetry(ctx, reconnectReason, attempt, startOfRetries) { 423 return 424 } 425 } 426 427 writer, err := w.startWriteStream(ctx, streamCtx, attempt) 428 w.onWriterChange(writer) 429 if err == nil { 430 reconnectReason = writer.WaitClose(ctx) 431 startOfRetries = time.Now() 432 } else { 433 reconnectReason = err 434 } 435 } 436 } 437 438 func (w *WriterReconnector) handleReconnectRetry( 439 ctx context.Context, 440 reconnectReason error, 441 attempt int, 442 startOfRetries time.Time, 443 ) bool { 444 retryDuration := w.cfg.clock.Since(startOfRetries) 445 if backoff, stopRetryReason := topic.RetryDecision( 446 reconnectReason, 447 w.retrySettings, 448 retryDuration, 449 ); stopRetryReason == nil { 450 delay := backoff.Delay(attempt) 451 delayTimer := w.cfg.clock.NewTimer(delay) 452 select { 453 case <-ctx.Done(): 454 delayTimer.Stop() 455 456 return true 457 case <-delayTimer.Chan(): 458 delayTimer.Stop() // no really need, stop for common style only 459 // pass 460 } 461 } else { 462 _ = w.close(ctx, fmt.Errorf("%w, was retried (%v)", stopRetryReason, retryDuration)) 463 464 return true 465 } 466 467 return false 468 } 469 470 func (w *WriterReconnector) startWriteStream(ctx, streamCtx context.Context, attempt int) ( 471 writer *SingleStreamWriter, 472 err error, 473 ) { 474 traceOnDone := trace.TopicOnWriterReconnect( 475 w.cfg.Tracer, 476 w.writerInstanceID, 477 w.cfg.topic, 478 w.cfg.producerID, 479 attempt, 480 ) 481 defer func() { 482 traceOnDone(err) 483 }() 484 485 stream, err := w.connectWithTimeout(streamCtx) 486 if err != nil { 487 return nil, err 488 } 489 490 w.queue.ResetSentProgress() 491 492 return NewSingleStreamWriter(ctx, w.createWriterStreamConfig(stream)) 493 } 494 495 func (w *WriterReconnector) needReceiveLastSeqNo() bool { 496 res := !w.firstConnectionHandled.Load() 497 498 return res 499 } 500 501 func (w *WriterReconnector) connectWithTimeout(streamLifetimeContext context.Context) (RawTopicWriterStream, error) { 502 connectCtx, connectCancel := xcontext.WithCancel(streamLifetimeContext) 503 504 type resT struct { 505 stream RawTopicWriterStream 506 err error 507 } 508 resCh := make(chan resT, 1) 509 510 go func() { 511 defer func() { 512 p := recover() 513 if p != nil { 514 resCh <- resT{ 515 stream: nil, 516 err: xerrors.WithStackTrace(xerrors.Wrap(fmt.Errorf("ydb: panic while connect to topic writer: %+v", p))), 517 } 518 } 519 }() 520 521 stream, err := w.cfg.Connect(connectCtx) 522 resCh <- resT{stream: stream, err: err} 523 }() 524 525 timer := time.NewTimer(w.cfg.connectTimeout) 526 defer timer.Stop() 527 528 select { 529 case <-timer.C: 530 connectCancel() 531 532 return nil, xerrors.WithStackTrace(errConnTimeout) 533 case res := <-resCh: 534 // force no cancel connect context - because it will break stream 535 // context will cancel by cancel streamLifetimeContext while reconnect or stop connection 536 _ = connectCancel 537 538 return res.stream, res.err 539 } 540 } 541 542 func (w *WriterReconnector) onAckReceived(count int) { 543 w.semaphore.Release(int64(count)) 544 } 545 546 func (w *WriterReconnector) onWriterChange(writerStream *SingleStreamWriter) { 547 isFirstInit := false 548 w.m.WithLock(func() { 549 if writerStream == nil { 550 w.sessionID = "" 551 552 return 553 } 554 w.sessionID = writerStream.SessionID 555 556 if !w.firstConnectionHandled.CompareAndSwap(false, true) { 557 return 558 } 559 defer close(w.firstInitResponseProcessedChan) 560 isFirstInit = true 561 562 if writerStream.LastSeqNumRequested { 563 w.lastSeqNo = writerStream.ReceivedLastSeqNum 564 } 565 }) 566 567 if isFirstInit { 568 w.m.WithLock(func() { 569 w.initDone = true 570 w.initInfo = InitialInfo{LastSeqNum: w.lastSeqNo} 571 close(w.initDoneCh) 572 }) 573 w.onWriterInitCallbackHandler(writerStream) 574 } 575 } 576 577 func (w *WriterReconnector) WaitInit(ctx context.Context) (info InitialInfo, err error) { 578 if ctx.Err() != nil { 579 return InitialInfo{}, ctx.Err() 580 } 581 582 select { 583 case <-ctx.Done(): 584 return InitialInfo{}, ctx.Err() 585 case <-w.background.Done(): 586 return InitialInfo{}, w.background.CloseReason() 587 case <-w.initDoneCh: 588 return w.initInfo, nil 589 } 590 } 591 592 func (w *WriterReconnector) onWriterInitCallbackHandler(writerStream *SingleStreamWriter) { 593 if w.cfg.OnWriterInitResponseCallback != nil { 594 info := PublicWithOnWriterConnectedInfo{ 595 LastSeqNo: w.lastSeqNo, 596 SessionID: w.sessionID, 597 PartitionID: writerStream.PartitionID, 598 CodecsFromServer: createPublicCodecsFromRaw(writerStream.CodecsFromServer), 599 } 600 601 if err := w.cfg.OnWriterInitResponseCallback(info); err != nil { 602 _ = w.close(context.Background(), fmt.Errorf("OnWriterInitResponseCallback return error: %w", err)) 603 } 604 } 605 } 606 607 func (w *WriterReconnector) waitFirstInitResponse(ctx context.Context) error { 608 if err := ctx.Err(); err != nil { 609 return err 610 } 611 612 if w.firstConnectionHandled.Load() { 613 return nil 614 } 615 616 select { 617 case <-w.background.Done(): 618 return w.background.CloseReason() 619 case <-w.firstInitResponseProcessedChan: 620 return nil 621 case <-ctx.Done(): 622 return ctx.Err() 623 } 624 } 625 626 func (w *WriterReconnector) createWriterStreamConfig(stream RawTopicWriterStream) SingleStreamWriterConfig { 627 cfg := newSingleStreamWriterConfig( 628 w.cfg.WritersCommonConfig, 629 stream, 630 &w.queue, 631 w.encodersMap, 632 w.needReceiveLastSeqNo(), 633 w.writerInstanceID, 634 ) 635 636 return cfg 637 } 638 639 func (w *WriterReconnector) GetSessionID() (sessionID string) { 640 w.m.WithLock(func() { 641 sessionID = w.sessionID 642 }) 643 644 return sessionID 645 } 646 647 func sendMessagesToStream( 648 stream RawTopicWriterStream, 649 targetCodec rawtopiccommon.Codec, 650 messages []messageWithDataContent, 651 ) error { 652 if len(messages) == 0 { 653 return nil 654 } 655 656 request, err := createWriteRequest(messages, targetCodec) 657 if err != nil { 658 return err 659 } 660 err = stream.Send(&request) 661 if err != nil { 662 return xerrors.WithStackTrace(fmt.Errorf("ydb: failed send write request: %w", err)) 663 } 664 665 return nil 666 } 667 668 func allMessagesHasSameBufCodec(messages []messageWithDataContent) bool { 669 if len(messages) <= 1 { 670 return true 671 } 672 673 codec := messages[0].bufCodec 674 for i := range messages { 675 if messages[i].bufCodec != codec { 676 return false 677 } 678 } 679 680 return true 681 } 682 683 func splitMessagesByBufCodec(messages []messageWithDataContent) (res [][]messageWithDataContent) { 684 if len(messages) == 0 { 685 return nil 686 } 687 688 currentGroupStart := 0 689 currentCodec := messages[0].bufCodec 690 for i := range messages { 691 if messages[i].bufCodec != currentCodec { 692 res = append(res, messages[currentGroupStart:i:i]) 693 currentGroupStart = i 694 currentCodec = messages[i].bufCodec 695 } 696 } 697 res = append(res, messages[currentGroupStart:len(messages):len(messages)]) 698 699 return res 700 } 701 702 func createWriteRequest(messages []messageWithDataContent, targetCodec rawtopiccommon.Codec) ( 703 res rawtopicwriter.WriteRequest, 704 err error, 705 ) { 706 for i := 1; i < len(messages); i++ { 707 if messages[i-1].tx != messages[i].tx { 708 return res, xerrors.WithStackTrace(errDiffetentTransactions) 709 } 710 } 711 712 if len(messages) > 0 && messages[0].tx != nil { 713 res.Tx.ID = messages[0].tx.ID() 714 res.Tx.Session = messages[0].tx.SessionID() 715 } 716 717 res.Codec = targetCodec 718 res.Messages = make([]rawtopicwriter.MessageData, len(messages)) 719 for i := range messages { 720 res.Messages[i], err = createRawMessageData(res.Codec, &messages[i]) 721 if err != nil { 722 return res, err 723 } 724 } 725 726 return res, nil 727 } 728 729 func createRawMessageData( 730 codec rawtopiccommon.Codec, 731 mess *messageWithDataContent, 732 ) (res rawtopicwriter.MessageData, err error) { 733 res.CreatedAt = mess.CreatedAt 734 res.SeqNo = mess.SeqNo 735 736 switch { 737 case mess.futurePartitioning.hasPartitionID: 738 res.Partitioning.Type = rawtopicwriter.PartitioningPartitionID 739 res.Partitioning.PartitionID = mess.futurePartitioning.partitionID 740 case mess.futurePartitioning.messageGroupID != "": 741 res.Partitioning.Type = rawtopicwriter.PartitioningMessageGroupID 742 res.Partitioning.MessageGroupID = mess.futurePartitioning.messageGroupID 743 default: 744 // pass 745 } 746 747 res.UncompressedSize = int64(mess.BufUncompressedSize) 748 res.Data, err = mess.GetEncodedBytes(codec) 749 750 if len(mess.Metadata) > 0 { 751 res.MetadataItems = make([]rawtopiccommon.MetadataItem, 0, len(mess.Metadata)) 752 for key, val := range mess.Metadata { 753 res.MetadataItems = append(res.MetadataItems, rawtopiccommon.MetadataItem{ 754 Key: key, 755 Value: val, 756 }) 757 } 758 } 759 760 return res, err 761 } 762 763 func calculateAllowedCodecs(forceCodec rawtopiccommon.Codec, encoderMap *EncoderMap, 764 serverCodecs rawtopiccommon.SupportedCodecs, 765 ) rawtopiccommon.SupportedCodecs { 766 if forceCodec != rawtopiccommon.CodecUNSPECIFIED { 767 if serverCodecs.AllowedByCodecsList(forceCodec) && encoderMap.IsSupported(forceCodec) { 768 return rawtopiccommon.SupportedCodecs{forceCodec} 769 } 770 771 return nil 772 } 773 774 if len(serverCodecs) == 0 { 775 // fixed list for autoselect codec if empty server list for prevent unexpectedly add messages with new codec 776 // with sdk update 777 serverCodecs = rawtopiccommon.SupportedCodecs{rawtopiccommon.CodecRaw, rawtopiccommon.CodecGzip} 778 } 779 780 res := make(rawtopiccommon.SupportedCodecs, 0, len(serverCodecs)) 781 for _, codec := range serverCodecs { 782 if encoderMap.IsSupported(codec) { 783 res = append(res, codec) 784 } 785 } 786 if len(res) == 0 { 787 res = nil 788 } 789 790 return res 791 } 792 793 type ConnectFunc func(ctx context.Context) (RawTopicWriterStream, error) 794 795 func createPublicCodecsFromRaw(codecs rawtopiccommon.SupportedCodecs) []topictypes.Codec { 796 res := make([]topictypes.Codec, len(codecs)) 797 for i, v := range codecs { 798 res[i] = topictypes.Codec(v) 799 } 800 801 return res 802 }