github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/topic/topicwriterinternal/writer_reconnector_test.go (about) 1 package topicwriterinternal 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "context" 7 "errors" 8 "fmt" 9 "io" 10 "math" 11 "sort" 12 "sync" 13 "sync/atomic" 14 "testing" 15 "time" 16 17 "github.com/stretchr/testify/require" 18 "go.uber.org/mock/gomock" 19 20 "github.com/ydb-platform/ydb-go-sdk/v3/internal/empty" 21 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopiccommon" 22 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicwriter" 23 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawydb" 24 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 25 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 26 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" 27 ) 28 29 var testCommonEncoders = NewEncoderMap() 30 31 func TestWriterImpl_AutoSeq(t *testing.T) { 32 t.Run("OK", func(t *testing.T) { 33 ctx := xtest.Context(t) 34 w := newWriterReconnectorStopped(NewWriterReconnectorConfig( 35 WithAutoSetSeqNo(true), 36 WithAutosetCreatedTime(false), 37 )) 38 w.firstConnectionHandled.Store(true) 39 40 lastSeqNo := int64(16) 41 w.lastSeqNo = lastSeqNo 42 43 var wg sync.WaitGroup 44 fWrite := func(num int) { 45 defer wg.Done() 46 47 msgs := newTestMessages(0) 48 msgs[0].CreatedAt = time.Unix(int64(num), 0) 49 require.NoError(t, w.Write(ctx, msgs)) 50 } 51 52 const messCount = 1000 53 wg.Add(messCount) 54 for i := 0; i < messCount; i++ { 55 go fWrite(i) 56 } 57 wg.Wait() 58 59 require.Len(t, w.queue.messagesByOrder, messCount) 60 require.Equal(t, lastSeqNo+messCount, w.queue.lastSeqNo) 61 }) 62 63 t.Run("PredefinedSeqNo", func(t *testing.T) { 64 ctx := xtest.Context(t) 65 66 w := newWriterReconnectorStopped(NewWriterReconnectorConfig(WithAutoSetSeqNo(true))) 67 w.firstConnectionHandled.Store(true) 68 require.Error(t, w.Write(ctx, newTestMessages(1))) 69 }) 70 } 71 72 func TestWriterImpl_CheckMessages(t *testing.T) { 73 t.Run("MessageSize", func(t *testing.T) { 74 ctx := xtest.Context(t) 75 w := newWriterReconnectorStopped(NewWriterReconnectorConfig()) 76 w.firstConnectionHandled.Store(true) 77 78 maxSize := 5 79 w.cfg.MaxMessageSize = maxSize 80 81 err := w.Write(ctx, []PublicMessage{{Data: bytes.NewReader(make([]byte, maxSize))}}) 82 require.NoError(t, err) 83 84 err = w.Write(ctx, []PublicMessage{{Data: bytes.NewReader(make([]byte, maxSize+1))}}) 85 require.Error(t, err) 86 }) 87 } 88 89 func TestWriterImpl_Write(t *testing.T) { 90 t.Run("PushToQueue", func(t *testing.T) { 91 ctx := context.Background() 92 w := newTestWriterStopped() 93 w.cfg.AutoSetCreatedTime = false 94 w.firstConnectionHandled.Store(true) 95 96 err := w.Write(ctx, newTestMessages(1, 3, 5)) 97 require.NoError(t, err) 98 99 expectedMap := map[int]messageWithDataContent{ 100 1: newTestMessageWithDataContent(1), 101 2: newTestMessageWithDataContent(3), 102 3: newTestMessageWithDataContent(5), 103 } 104 105 for k := range expectedMap { 106 mess := expectedMap[k] 107 _, err = mess.GetEncodedBytes(rawtopiccommon.CodecRaw) 108 require.NoError(t, err) 109 mess.metadataCached = true 110 expectedMap[k] = mess 111 } 112 113 require.Equal(t, expectedMap, w.queue.messagesByOrder) 114 }) 115 t.Run("WriteWithSyncMode", func(t *testing.T) { 116 xtest.TestManyTimes(t, func(t testing.TB) { 117 e := newTestEnv(t, &testEnvOptions{ 118 writerOptions: []PublicWriterOption{ 119 WithWaitAckOnWrite(true), 120 }, 121 }) 122 123 messageTime := time.Date(2022, 9, 7, 11, 34, 0, 0, time.UTC) 124 messageData := []byte("123") 125 126 const seqNo = 31 127 128 writeMessageReceived := make(empty.Chan) 129 e.stream.EXPECT().Send(&rawtopicwriter.WriteRequest{ 130 Messages: []rawtopicwriter.MessageData{ 131 { 132 SeqNo: seqNo, 133 CreatedAt: messageTime, 134 UncompressedSize: int64(len(messageData)), 135 Partitioning: rawtopicwriter.Partitioning{}, 136 Data: messageData, 137 }, 138 }, 139 Codec: rawtopiccommon.CodecRaw, 140 }).DoAndReturn(func(_ rawtopicwriter.ClientMessage) error { 141 close(writeMessageReceived) 142 143 return nil 144 }) 145 146 writeCompleted := make(empty.Chan) 147 go func() { 148 err := e.writer.Write(e.ctx, []PublicMessage{{ 149 SeqNo: seqNo, 150 CreatedAt: messageTime, 151 Data: bytes.NewReader(messageData), 152 }}) 153 require.NoError(t, err) 154 close(writeCompleted) 155 }() 156 157 <-writeMessageReceived 158 159 select { 160 case <-writeCompleted: 161 t.Fatal("sync write must complete after receive ack only") 162 default: 163 // pass 164 } 165 166 e.sendFromServer(&rawtopicwriter.WriteResult{ 167 Acks: []rawtopicwriter.WriteAck{ 168 { 169 SeqNo: seqNo, 170 MessageWriteStatus: rawtopicwriter.MessageWriteStatus{ 171 Type: rawtopicwriter.WriteStatusTypeWritten, 172 WrittenOffset: 4, 173 }, 174 }, 175 }, 176 PartitionID: e.partitionID, 177 }) 178 179 xtest.WaitChannelClosed(t, writeCompleted) 180 }) 181 }) 182 } 183 184 func TestWriterImpl_WriteCodecs(t *testing.T) { 185 t.Run("ForceRaw", func(t *testing.T) { 186 var err error 187 e := newTestEnv(t, &testEnvOptions{writerOptions: []PublicWriterOption{WithCodec(rawtopiccommon.CodecRaw)}}) 188 189 messContent := []byte("123") 190 191 messReceived := make(chan rawtopiccommon.Codec, 2) 192 e.stream.EXPECT().Send(gomock.Any()).DoAndReturn(func(message rawtopicwriter.ClientMessage) error { 193 writeReq := message.(*rawtopicwriter.WriteRequest) 194 messReceived <- writeReq.Codec 195 196 return nil 197 }) 198 199 require.NoError(t, err) 200 require.NoError(t, e.writer.Write(e.ctx, []PublicMessage{{ 201 Data: bytes.NewReader(messContent), 202 }})) 203 204 mess := <-messReceived 205 require.Equal(t, rawtopiccommon.CodecRaw, mess) 206 }) 207 t.Run("ForceGzip", func(t *testing.T) { 208 var err error 209 e := newTestEnv(t, &testEnvOptions{ 210 writerOptions: []PublicWriterOption{WithCodec(rawtopiccommon.CodecGzip)}, 211 topicCodecs: rawtopiccommon.SupportedCodecs{rawtopiccommon.CodecGzip}, 212 }) 213 214 messContent := []byte("123") 215 216 gzipped := &bytes.Buffer{} 217 writer := gzip.NewWriter(gzipped) 218 _, err = writer.Write(messContent) 219 require.NoError(t, err) 220 require.NoError(t, writer.Close()) 221 222 messReceived := make(chan rawtopiccommon.Codec, 2) 223 e.stream.EXPECT().Send(gomock.Any()).DoAndReturn(func(message rawtopicwriter.ClientMessage) error { 224 writeReq := message.(*rawtopicwriter.WriteRequest) 225 messReceived <- writeReq.Codec 226 227 return nil 228 }) 229 230 require.NoError(t, err) 231 require.NoError(t, e.writer.Write(e.ctx, []PublicMessage{{ 232 Data: bytes.NewReader(messContent), 233 }})) 234 235 require.Equal(t, rawtopiccommon.CodecGzip, <-messReceived) 236 }) 237 t.Run("Auto", func(t *testing.T) { 238 e := newTestEnv(t, &testEnvOptions{ 239 writerOptions: []PublicWriterOption{ 240 WithAutoSetSeqNo(true), 241 WithAutoCodec(), 242 }, 243 topicCodecs: rawtopiccommon.SupportedCodecs{rawtopiccommon.CodecRaw, rawtopiccommon.CodecGzip}, 244 }) 245 246 messContentShort := []byte("1") 247 messContentLong := make([]byte, 100000) 248 249 messReceived := make(chan rawtopiccommon.Codec, 2) 250 e.stream.EXPECT().Send(gomock.Any()).DoAndReturn(func(message rawtopicwriter.ClientMessage) error { 251 writeReq := message.(*rawtopicwriter.WriteRequest) 252 messReceived <- writeReq.Codec 253 254 return nil 255 }).Times(codecMeasureIntervalBatches * 2) 256 257 codecs := make(map[rawtopiccommon.Codec]empty.Struct) 258 259 for i := 0; i < codecMeasureIntervalBatches; i++ { 260 require.NoError(t, e.writer.Write(e.ctx, []PublicMessage{{ 261 Data: bytes.NewReader(messContentShort), 262 }})) 263 // wait send 264 codec := <-messReceived 265 codecs[codec] = empty.Struct{} 266 } 267 268 for i := 0; i < codecMeasureIntervalBatches; i++ { 269 require.NoError(t, e.writer.Write(e.ctx, []PublicMessage{{ 270 Data: bytes.NewReader(messContentLong), 271 }})) 272 // wait send 273 codec := <-messReceived 274 codecs[codec] = empty.Struct{} 275 } 276 277 // used two different codecs 278 require.Len(t, codecs, 2) 279 }) 280 } 281 282 func TestWriterReconnector_Write_QueueLimit(t *testing.T) { 283 xtest.TestManyTimes(t, func(t testing.TB) { 284 ctx := xtest.Context(t) 285 w := newWriterReconnectorStopped(NewWriterReconnectorConfig( 286 WithAutoSetSeqNo(false), 287 WithMaxQueueLen(2), 288 )) 289 w.firstConnectionHandled.Store(true) 290 291 waitStartQueueWait := func(targetWaiters int) { 292 xtest.SpinWaitCondition(t, nil, func() bool { 293 res := getWaitersCount(w.semaphore) == targetWaiters 294 295 return res 296 }) 297 } 298 299 err := w.Write(ctx, newTestMessages(1, 2)) 300 require.NoError(t, err) 301 302 ctxNoQueueSpace, ctxNoQueueSpaceCancel := xcontext.WithCancel(ctx) 303 304 go func() { 305 waitStartQueueWait(1) 306 ctxNoQueueSpaceCancel() 307 }() 308 err = w.Write(ctxNoQueueSpace, newTestMessages(3)) 309 require.Error(t, err) 310 require.NotErrorIs(t, err, PublicErrMessagesPutToInternalQueueBeforeError) 311 312 go func() { 313 waitStartQueueWait(1) 314 ackErr := w.queue.AcksReceived([]rawtopicwriter.WriteAck{ 315 { 316 SeqNo: 1, 317 }, 318 }) 319 require.NoError(t, ackErr) 320 }() 321 322 err = w.Write(ctx, newTestMessages(3)) 323 require.NoError(t, err) 324 }) 325 } 326 327 func TestMessagesPutToInternalQueueBeforeError(t *testing.T) { 328 ctx := xtest.Context(t) 329 w := newWriterReconnectorStopped(NewWriterReconnectorConfig( 330 WithAutoSetSeqNo(false), 331 WithMaxQueueLen(2), 332 WithWaitAckOnWrite(true), 333 )) 334 w.firstConnectionHandled.Store(true) 335 336 ctxCancel, cancel := context.WithCancel(ctx) 337 go func() { 338 <-w.queue.hasNewMessages 339 cancel() 340 }() 341 err := w.Write(ctxCancel, newTestMessages(1)) 342 require.ErrorIs(t, err, PublicErrMessagesPutToInternalQueueBeforeError) 343 } 344 345 func TestEnv(t *testing.T) { 346 xtest.TestManyTimes(t, func(t testing.TB) { 347 env := newTestEnv(t, nil) 348 xtest.WaitChannelClosed(t, env.writer.firstInitResponseProcessedChan) 349 }) 350 } 351 352 func TestWriterImpl_InitSession(t *testing.T) { 353 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 354 lastSeqNo := int64(123) 355 sessionID := "test-session-id" 356 357 w.onWriterChange(&SingleStreamWriter{ 358 ReceivedLastSeqNum: lastSeqNo, 359 LastSeqNumRequested: true, 360 SessionID: sessionID, 361 }) 362 363 require.Equal(t, sessionID, w.sessionID) 364 require.Equal(t, lastSeqNo, w.lastSeqNo) 365 require.True(t, isClosed(w.firstInitResponseProcessedChan)) 366 } 367 368 func TestWriterImpl_WaitInit(t *testing.T) { 369 t.Run("OK", func(t *testing.T) { 370 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 371 expectedInitData := InitialInfo{ 372 LastSeqNum: int64(123), 373 } 374 w.onWriterChange(&SingleStreamWriter{ 375 ReceivedLastSeqNum: expectedInitData.LastSeqNum, 376 LastSeqNumRequested: true, 377 }) 378 379 initData, err := w.WaitInit(context.Background()) 380 require.NoError(t, err) 381 require.Equal(t, expectedInitData, initData) 382 383 err = w.Write(context.Background(), newTestMessages(0)) 384 require.NoError(t, err) 385 386 // one more run is needed to check idempotency 387 anotherInitData, err := w.WaitInit(context.Background()) 388 require.NoError(t, err) 389 require.Equal(t, initData, anotherInitData) 390 391 require.True(t, isClosed(w.firstInitResponseProcessedChan)) 392 }) 393 394 t.Run("contextDeadlineErrorInProgress", func(t *testing.T) { 395 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 396 ctx, cancel := context.WithCancel(context.Background()) 397 398 go func() { 399 // wait until w.WaitInit starts 400 time.Sleep(time.Millisecond) 401 cancel() 402 }() 403 404 _, err := w.WaitInit(ctx) 405 require.ErrorIs(t, err, ctx.Err()) 406 }) 407 408 t.Run("contextDeadlineErrorBeforeStart", func(t *testing.T) { 409 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 410 ctx, cancel := context.WithCancel(context.Background()) 411 cancel() 412 _, err := w.WaitInit(ctx) 413 require.ErrorIs(t, err, ctx.Err()) 414 415 w.onWriterChange(&SingleStreamWriter{}) 416 require.True(t, isClosed(w.firstInitResponseProcessedChan)) 417 }) 418 419 t.Run("CloseWriter", func(t *testing.T) { 420 ctx := context.Background() 421 w := newTestWriterStopped(WithAutoSetSeqNo(true)) 422 423 testErr := errors.New("test error") 424 go func() { 425 _ = w.close(ctx, testErr) 426 }() 427 428 _, err := w.WaitInit(ctx) 429 require.ErrorIs(t, err, testErr) 430 431 w.onWriterChange(&SingleStreamWriter{}) 432 require.True(t, isClosed(w.firstInitResponseProcessedChan)) 433 }) 434 } 435 436 func TestWriterImpl_Reconnect(t *testing.T) { 437 t.Run("StopReconnectOnUnretryableError", func(t *testing.T) { 438 mc := gomock.NewController(t) 439 strm := NewMockRawTopicWriterStream(mc) 440 441 w := newTestWriterStopped() 442 443 ctx := xtest.Context(t) 444 testErr := errors.New("test") 445 446 connectCalled := false 447 connectCalledChan := make(empty.Chan) 448 449 w.cfg.Connect = func(streamCtxArg context.Context) (RawTopicWriterStream, error) { 450 close(connectCalledChan) 451 connectCalled = true 452 require.NotEqual(t, ctx, streamCtxArg) 453 454 return strm, nil 455 } 456 457 initRequest := testCreateInitRequest(w) 458 strm.EXPECT().Send(&initRequest) 459 strm.EXPECT().Recv().Return(nil, testErr) 460 strm.EXPECT().CloseSend() 461 462 w.connectionLoop(ctx) 463 464 require.True(t, connectCalled) 465 require.ErrorIs(t, w.background.CloseReason(), testErr) 466 }) 467 468 xtest.TestManyTimesWithName(t, "ReconnectOnErrors", func(t testing.TB) { 469 ctx := xtest.Context(t) 470 471 w := newTestWriterStopped(WithClock(xtest.FastClock(t)), WithTokenUpdateInterval(time.Duration(math.MaxInt64))) 472 473 mc := gomock.NewController(t) 474 475 type connectionAttemptContext struct { 476 name string 477 stream RawTopicWriterStream 478 connectionError error 479 } 480 481 isFirstConnection := true 482 newStream := func(name string) *MockRawTopicWriterStream { 483 strm := NewMockRawTopicWriterStream(mc) 484 initReq := testCreateInitRequest(w) 485 if isFirstConnection { 486 isFirstConnection = false 487 } else { 488 initReq.GetLastSeqNo = false 489 } 490 491 streamClosed := make(empty.Chan) 492 strm.EXPECT().CloseSend().DoAndReturn(func() error { 493 t.Logf("closed stream: %v", name) 494 close(streamClosed) 495 496 return nil 497 }) 498 499 strm.EXPECT().Send(&initReq).DoAndReturn(func(_ rawtopicwriter.ClientMessage) error { 500 t.Logf("sent init request stream: %v", name) 501 502 return nil 503 }) 504 505 strm.EXPECT().Recv().DoAndReturn(func() (rawtopicwriter.ServerMessage, error) { 506 t.Logf("receive init response stream: %v", name) 507 508 return &rawtopicwriter.InitResult{ 509 ServerMessageMetadata: rawtopiccommon.ServerMessageMetadata{Status: rawydb.StatusSuccess}, 510 SessionID: name, 511 }, nil 512 }) 513 514 strm.EXPECT().Recv().DoAndReturn(func() (rawtopicwriter.ServerMessage, error) { 515 xtest.WaitChannelClosed(t, streamClosed) 516 t.Logf("channel closed: %v", name) 517 518 return nil, errors.New("test stream closed") 519 }).MaxTimes(1) 520 521 return strm 522 } 523 524 strm2 := newStream("strm2") 525 strm2.EXPECT().Send(&rawtopicwriter.WriteRequest{ 526 Messages: []rawtopicwriter.MessageData{ 527 {SeqNo: 1}, 528 }, 529 Codec: rawtopiccommon.CodecRaw, 530 }).DoAndReturn(func(_ rawtopicwriter.ClientMessage) error { 531 t.Logf("strm2 sent message and return retriable error") 532 533 return xerrors.Retryable(errors.New("retriable on strm2")) 534 }) 535 536 strm3 := newStream("strm3") 537 strm3.EXPECT().Send(&rawtopicwriter.WriteRequest{ 538 Messages: []rawtopicwriter.MessageData{ 539 {SeqNo: 1}, 540 }, 541 Codec: rawtopiccommon.CodecRaw, 542 }).DoAndReturn(func(_ rawtopicwriter.ClientMessage) error { 543 t.Logf("strm3 sent message and return unretriable error") 544 545 return errors.New("strm3") 546 }) 547 548 connectsResult := []connectionAttemptContext{ 549 { 550 name: "step-1 connection error", 551 stream: nil, 552 connectionError: xerrors.Retryable(errors.New("test-1")), 553 }, 554 { 555 name: "step-2 connect and return retryable error on write", 556 stream: strm2, 557 }, 558 { 559 name: "step-3 connect and return unretriable error on write", 560 stream: strm3, 561 }, 562 } 563 564 var connectionAttempt atomic.Int64 565 w.cfg.Connect = func(ctx context.Context) (RawTopicWriterStream, error) { 566 attemptIndex := int(connectionAttempt.Add(1)) - 1 567 t.Logf("connect with attempt index: %v", attemptIndex) 568 res := connectsResult[attemptIndex] 569 570 return res.stream, res.connectionError 571 } 572 573 connectionLoopStopped := make(empty.Chan) 574 go func() { 575 defer close(connectionLoopStopped) 576 w.connectionLoop(ctx) 577 t.Log("connection loop stopped") 578 }() 579 580 err := w.Write(ctx, newTestMessages(1)) 581 require.NoError(t, err) 582 583 t.Log("Waiting to connection loop stopped...") 584 xtest.WaitChannelClosedWithTimeout(t, connectionLoopStopped, 4*time.Second) 585 t.Log("Connection loop stopped") 586 }) 587 } 588 589 func TestWriterImpl_CloseWithFlush(t *testing.T) { 590 type flushMethod func(ctx context.Context, writer *WriterReconnector) error 591 592 f := func(t testing.TB, flush flushMethod) { 593 e := newTestEnv(t, nil) 594 595 messageTime := time.Date(2023, 9, 7, 11, 34, 0, 0, time.UTC) 596 messageData := []byte("123") 597 598 const seqNo = 36 599 600 writeCompleted := make(empty.Chan) 601 e.stream.EXPECT().Send(&rawtopicwriter.WriteRequest{ 602 Messages: []rawtopicwriter.MessageData{ 603 { 604 SeqNo: seqNo, 605 CreatedAt: messageTime, 606 UncompressedSize: int64(len(messageData)), 607 Partitioning: rawtopicwriter.Partitioning{}, 608 Data: messageData, 609 }, 610 }, 611 Codec: rawtopiccommon.CodecRaw, 612 }).DoAndReturn(func(_ rawtopicwriter.ClientMessage) error { 613 close(writeCompleted) 614 615 return nil 616 }) 617 618 flushCompleted := make(empty.Chan) 619 go func() { 620 err := e.writer.Write(e.ctx, []PublicMessage{{ 621 SeqNo: seqNo, 622 CreatedAt: messageTime, 623 Data: bytes.NewReader(messageData), 624 }}) 625 require.NoError(t, err) 626 }() 627 628 <-writeCompleted 629 630 go func() { 631 require.NoError(t, flush(e.ctx, e.writer)) 632 close(flushCompleted) 633 }() 634 635 select { 636 case <-flushCompleted: 637 t.Fatal("flush and close must complete only after message is acked") 638 case <-time.After(10 * time.Millisecond): 639 // pass 640 } 641 642 e.sendFromServer(&rawtopicwriter.WriteResult{ 643 Acks: []rawtopicwriter.WriteAck{ 644 { 645 SeqNo: seqNo, 646 MessageWriteStatus: rawtopicwriter.MessageWriteStatus{ 647 Type: rawtopicwriter.WriteStatusTypeWritten, 648 WrittenOffset: 4, 649 }, 650 }, 651 }, 652 PartitionID: e.partitionID, 653 }) 654 655 xtest.WaitChannelClosed(t, flushCompleted) 656 } 657 658 tests := []struct { 659 name string 660 flush flushMethod 661 }{ 662 { 663 name: "close", 664 flush: func(ctx context.Context, writer *WriterReconnector) error { 665 return writer.Close(ctx) 666 }, 667 }, 668 { 669 name: "flush", 670 flush: func(ctx context.Context, writer *WriterReconnector) error { 671 return writer.Flush(ctx) 672 }, 673 }, 674 { 675 name: "flush_and_close", 676 flush: func(ctx context.Context, writer *WriterReconnector) error { 677 err := writer.Flush(ctx) 678 if err != nil { 679 return err 680 } 681 682 return writer.Close(ctx) 683 }, 684 }, 685 } 686 687 for _, test := range tests { 688 t.Run(test.name, func(t *testing.T) { 689 xtest.TestManyTimes(t, func(t testing.TB) { 690 f(t, test.flush) 691 }) 692 }) 693 } 694 } 695 696 func TestAllMessagesHasSameBufCodec(t *testing.T) { 697 t.Run("Empty", func(t *testing.T) { 698 require.True(t, allMessagesHasSameBufCodec(nil)) 699 }) 700 701 t.Run("One", func(t *testing.T) { 702 require.True(t, allMessagesHasSameBufCodec(newTestMessagesWithContent(1))) 703 }) 704 705 t.Run("SameCodecs", func(t *testing.T) { 706 require.True(t, allMessagesHasSameBufCodec(newTestMessagesWithContent(1, 2, 3))) 707 }) 708 t.Run("DifferCodecs", func(t *testing.T) { 709 for i := 0; i < 3; i++ { 710 messages := newTestMessagesWithContent(1, 2, 3) 711 messages[i].bufCodec = rawtopiccommon.CodecGzip 712 require.False(t, allMessagesHasSameBufCodec(messages)) 713 } 714 }) 715 } 716 717 func TestCreateRawMessageData(t *testing.T) { 718 t.Run("Empty", func(t *testing.T) { 719 req, err := createWriteRequest(newTestMessagesWithContent(), rawtopiccommon.CodecRaw) 720 require.NoError(t, err) 721 require.Equal(t, 722 rawtopicwriter.WriteRequest{ 723 Messages: []rawtopicwriter.MessageData{}, 724 Codec: rawtopiccommon.CodecRaw, 725 }, 726 req, 727 ) 728 }) 729 t.Run("WithMessageMetadata", func(t *testing.T) { 730 messages := newTestMessagesWithContent(1) 731 messages[0].Metadata = map[string][]byte{ 732 "a": {1, 2, 3}, 733 "b": {4, 5}, 734 } 735 req, err := createWriteRequest(messages, rawtopiccommon.CodecRaw) 736 737 sort.Slice(req.Messages[0].MetadataItems, func(i, j int) bool { 738 return req.Messages[0].MetadataItems[i].Key < req.Messages[0].MetadataItems[j].Key 739 }) 740 741 require.NoError(t, err) 742 require.Equal(t, rawtopicwriter.WriteRequest{ 743 Messages: []rawtopicwriter.MessageData{ 744 { 745 SeqNo: 1, 746 MetadataItems: []rawtopiccommon.MetadataItem{ 747 { 748 Key: "a", 749 Value: []byte{1, 2, 3}, 750 }, 751 { 752 Key: "b", 753 Value: []byte{4, 5}, 754 }, 755 }, 756 }, 757 }, 758 Codec: rawtopiccommon.CodecRaw, 759 }, req) 760 }) 761 t.Run("WithSeqno", func(t *testing.T) { 762 req, err := createWriteRequest(newTestMessagesWithContent(1, 2, 3), rawtopiccommon.CodecRaw) 763 require.NoError(t, err) 764 require.Equal(t, 765 rawtopicwriter.WriteRequest{ 766 Messages: []rawtopicwriter.MessageData{ 767 { 768 SeqNo: 1, 769 }, 770 { 771 SeqNo: 2, 772 }, 773 { 774 SeqNo: 3, 775 }, 776 }, 777 Codec: rawtopiccommon.CodecRaw, 778 }, 779 req, 780 ) 781 }) 782 } 783 784 func TestSplitMessagesByBufCodec(t *testing.T) { 785 tests := [][]rawtopiccommon.Codec{ 786 nil, 787 {}, 788 {rawtopiccommon.CodecRaw}, 789 {rawtopiccommon.CodecRaw, rawtopiccommon.CodecRaw}, 790 {rawtopiccommon.CodecRaw, rawtopiccommon.CodecGzip}, 791 { 792 rawtopiccommon.CodecRaw, 793 rawtopiccommon.CodecGzip, 794 rawtopiccommon.CodecGzip, 795 rawtopiccommon.CodecRaw, 796 rawtopiccommon.CodecGzip, 797 rawtopiccommon.CodecRaw, 798 rawtopiccommon.CodecRaw, 799 }, 800 } 801 802 for _, test := range tests { 803 t.Run(fmt.Sprint(test), func(t *testing.T) { 804 var messages []messageWithDataContent 805 for index, codec := range test { 806 mess := newTestMessageWithDataContent(index) 807 mess.bufCodec = codec 808 messages = append(messages, mess) 809 } 810 811 groups := splitMessagesByBufCodec(messages) 812 expectedNum := int64(-1) 813 for _, group := range groups { 814 require.NotEmpty(t, group) 815 require.True(t, allMessagesHasSameBufCodec(group)) 816 require.Len(t, group, cap(group)) 817 for _, mess := range group { 818 expectedNum++ 819 require.Equal(t, test[int(expectedNum)], mess.bufCodec) 820 mess.SeqNo = expectedNum 821 } 822 } 823 824 require.Equal(t, int(expectedNum), len(test)-1) 825 }) 826 } 827 } 828 829 func TestCalculateAllowedCodecs(t *testing.T) { 830 customCodecSupported := rawtopiccommon.Codec(rawtopiccommon.CodecCustomerFirst) 831 customCodecUnsupported := rawtopiccommon.Codec(rawtopiccommon.CodecCustomerFirst + 1) 832 encoders := NewEncoderMap() 833 encoders.AddEncoder(customCodecSupported, func(writer io.Writer) (io.WriteCloser, error) { 834 return nil, errors.New("test") 835 }) 836 837 table := []struct { 838 name string 839 force rawtopiccommon.Codec 840 serverCodecs rawtopiccommon.SupportedCodecs 841 expectedResult rawtopiccommon.SupportedCodecs 842 }{ 843 { 844 name: "ForceRawWithEmptyServer", 845 force: rawtopiccommon.CodecRaw, 846 serverCodecs: nil, 847 expectedResult: rawtopiccommon.SupportedCodecs{ 848 rawtopiccommon.CodecRaw, 849 }, 850 }, 851 { 852 name: "ForceRawWithAllowedByServer", 853 force: rawtopiccommon.CodecRaw, 854 serverCodecs: rawtopiccommon.SupportedCodecs{ 855 rawtopiccommon.CodecRaw, 856 rawtopiccommon.CodecGzip, 857 }, 858 expectedResult: rawtopiccommon.SupportedCodecs{ 859 rawtopiccommon.CodecRaw, 860 }, 861 }, 862 { 863 name: "ForceCustomWithAllowedByServer", 864 force: customCodecSupported, 865 serverCodecs: rawtopiccommon.SupportedCodecs{ 866 rawtopiccommon.CodecRaw, 867 rawtopiccommon.CodecGzip, 868 customCodecSupported, 869 }, 870 expectedResult: rawtopiccommon.SupportedCodecs{ 871 customCodecSupported, 872 }, 873 }, 874 { 875 name: "ForceRawWithDeniedByServer", 876 force: rawtopiccommon.CodecRaw, 877 serverCodecs: rawtopiccommon.SupportedCodecs{ 878 rawtopiccommon.CodecGzip, 879 }, 880 expectedResult: nil, 881 }, 882 { 883 name: "NotForcedWithEmptyServerList", 884 force: rawtopiccommon.CodecUNSPECIFIED, 885 serverCodecs: nil, 886 expectedResult: rawtopiccommon.SupportedCodecs{ 887 rawtopiccommon.CodecRaw, 888 rawtopiccommon.CodecGzip, 889 }, 890 }, 891 { 892 name: "NotForcedWithServerGzipOnly", 893 force: rawtopiccommon.CodecUNSPECIFIED, 894 serverCodecs: rawtopiccommon.SupportedCodecs{ 895 rawtopiccommon.CodecGzip, 896 }, 897 expectedResult: rawtopiccommon.SupportedCodecs{ 898 rawtopiccommon.CodecGzip, 899 }, 900 }, 901 { 902 name: "NotForcedCustomCodecSupportedAndAllowedByServer", 903 force: rawtopiccommon.CodecUNSPECIFIED, 904 serverCodecs: rawtopiccommon.SupportedCodecs{ 905 rawtopiccommon.CodecGzip, 906 customCodecSupported, 907 customCodecUnsupported, 908 }, 909 expectedResult: rawtopiccommon.SupportedCodecs{ 910 rawtopiccommon.CodecGzip, 911 customCodecSupported, 912 }, 913 }, 914 } 915 916 for _, test := range table { 917 t.Run(test.name, func(t *testing.T) { 918 res := calculateAllowedCodecs(test.force, encoders, test.serverCodecs) 919 require.Equal(t, test.expectedResult, res) 920 }) 921 } 922 } 923 924 func TestWriterReconnector_WaitInit(t *testing.T) { 925 t.Run("SuccessInit", func(t *testing.T) { 926 xtest.TestManyTimes(t, func(t testing.TB) { 927 initRequestReceived := make(empty.Chan) 928 env := newTestEnv(t, &testEnvOptions{ 929 skipWaitInitResponse: true, 930 customInitRequestHandler: func(env *testEnv, req *rawtopicwriter.InitRequest) { 931 close(initRequestReceived) 932 }, 933 }) 934 <-initRequestReceived 935 go func() { 936 time.Sleep(time.Millisecond) 937 env.sendFromServer(&rawtopicwriter.InitResult{ 938 ServerMessageMetadata: rawtopiccommon.ServerMessageMetadata{}, 939 LastSeqNo: 0, 940 SessionID: "session-" + t.Name(), 941 PartitionID: env.partitionID, 942 SupportedCodecs: rawtopiccommon.SupportedCodecs{rawtopiccommon.CodecRaw}, 943 }) 944 }() 945 _, err := env.writer.WaitInit(env.ctx) 946 require.NoError(t, err) 947 }) 948 }) 949 t.Run("Close", func(t *testing.T) { 950 xtest.TestManyTimes(t, func(t testing.TB) { 951 testErr := errors.New("test err") 952 initRequestReceived := make(empty.Chan) 953 env := newTestEnv(t, &testEnvOptions{ 954 skipWaitInitResponse: true, 955 customInitRequestHandler: func(env *testEnv, req *rawtopicwriter.InitRequest) { 956 close(initRequestReceived) 957 }, 958 }) 959 <-initRequestReceived 960 go func() { 961 time.Sleep(time.Millisecond) 962 _ = env.writer.close(env.ctx, testErr) 963 }() 964 _, err := env.writer.WaitInit(env.ctx) 965 require.ErrorIs(t, err, testErr) 966 }) 967 }) 968 t.Run("InitContext", func(t *testing.T) { 969 xtest.TestManyTimes(t, func(t testing.TB) { 970 initRequestReceived := make(empty.Chan) 971 env := newTestEnv(t, &testEnvOptions{ 972 skipWaitInitResponse: true, 973 customInitRequestHandler: func(env *testEnv, req *rawtopicwriter.InitRequest) { 974 close(initRequestReceived) 975 }, 976 }) 977 <-initRequestReceived 978 979 ctx, cancel := context.WithTimeout(env.ctx, time.Millisecond) 980 defer cancel() 981 982 _, err := env.writer.WaitInit(ctx) 983 require.ErrorIs(t, err, context.DeadlineExceeded) 984 }) 985 }) 986 } 987 988 func newTestMessageWithDataContent(num int) messageWithDataContent { 989 res := newMessageDataWithContent(PublicMessage{SeqNo: int64(num)}, testCommonEncoders) 990 991 return res 992 } 993 994 func newTestMessages(numbers ...int) []PublicMessage { 995 messages := make([]PublicMessage, len(numbers)) 996 for i, num := range numbers { 997 messages[i].SeqNo = int64(num) 998 } 999 1000 return messages 1001 } 1002 1003 func newTestMessagesWithContent(numbers ...int) []messageWithDataContent { 1004 messages := make([]messageWithDataContent, 0, len(numbers)) 1005 for _, num := range numbers { 1006 messages = append(messages, newTestMessageWithDataContent(num)) 1007 } 1008 1009 return messages 1010 } 1011 1012 func newTestWriterStopped(opts ...PublicWriterOption) *WriterReconnector { 1013 cfgOptions := append(defaultTestWriterOptions(), opts...) 1014 cfg := NewWriterReconnectorConfig(cfgOptions...) 1015 res := newWriterReconnectorStopped(cfg) 1016 1017 if cfg.AdditionalEncoders == nil { 1018 res.encodersMap = testCommonEncoders 1019 } 1020 1021 return res 1022 } 1023 1024 func defaultTestWriterOptions() []PublicWriterOption { 1025 return []PublicWriterOption{ 1026 WithProducerID("test-producer-id"), 1027 WithTopic("test-topic"), 1028 WithSessionMeta(map[string]string{"test-key": "test-val"}), 1029 WithPartitioning(NewPartitioningWithMessageGroupID("test-message-group-id")), 1030 WithAutoSetSeqNo(false), 1031 WithWaitAckOnWrite(false), 1032 WithCodec(rawtopiccommon.CodecRaw), 1033 WithAutosetCreatedTime(false), 1034 } 1035 } 1036 1037 func isClosed(ch <-chan struct{}) bool { 1038 select { 1039 case _, existVal := <-ch: 1040 if existVal { 1041 panic("value, when not expected") 1042 } 1043 1044 return true 1045 default: 1046 return false 1047 } 1048 } 1049 1050 type testEnv struct { 1051 ctx context.Context //nolint:containedctx 1052 stream *MockRawTopicWriterStream 1053 writer *WriterReconnector 1054 sendFromServerChannel chan sendFromServerResponse 1055 stopReadEvents empty.Chan 1056 partitionID int64 1057 connectCount int64 1058 } 1059 1060 type testEnvOptions struct { 1061 writerOptions []PublicWriterOption 1062 lastSeqNo int64 1063 topicCodecs rawtopiccommon.SupportedCodecs 1064 customInitRequestHandler func(env *testEnv, req *rawtopicwriter.InitRequest) 1065 skipWaitInitResponse bool 1066 } 1067 1068 func newTestEnv(t testing.TB, options *testEnvOptions) *testEnv { 1069 if options == nil { 1070 options = &testEnvOptions{} 1071 } 1072 1073 res := &testEnv{ 1074 ctx: xtest.Context(t), 1075 stream: NewMockRawTopicWriterStream(gomock.NewController(t)), 1076 sendFromServerChannel: make(chan sendFromServerResponse, 1), 1077 stopReadEvents: make(empty.Chan), 1078 partitionID: 14, 1079 } 1080 1081 writerOptions := append(defaultTestWriterOptions(), WithConnectFunc(func(ctx context.Context) ( 1082 RawTopicWriterStream, 1083 error, 1084 ) { 1085 connectNum := atomic.AddInt64(&res.connectCount, 1) 1086 if connectNum > 1 { 1087 t.Fatalf("test: default env support most one connection") 1088 } 1089 1090 return res.stream, nil 1091 })) 1092 writerOptions = append(writerOptions, options.writerOptions...) 1093 1094 res.writer = newWriterReconnectorStopped(NewWriterReconnectorConfig(writerOptions...)) 1095 1096 res.stream.EXPECT().Recv().DoAndReturn(res.receiveMessageHandler).AnyTimes() 1097 1098 req := testCreateInitRequest(res.writer) 1099 1100 if options.customInitRequestHandler == nil { 1101 res.stream.EXPECT().Send(&req).DoAndReturn(func(_ rawtopicwriter.ClientMessage) error { 1102 supportedCodecs := rawtopiccommon.SupportedCodecs{rawtopiccommon.CodecRaw} 1103 if options.topicCodecs != nil { 1104 supportedCodecs = options.topicCodecs 1105 } 1106 res.sendFromServer(&rawtopicwriter.InitResult{ 1107 ServerMessageMetadata: rawtopiccommon.ServerMessageMetadata{}, 1108 LastSeqNo: options.lastSeqNo, 1109 SessionID: "session-" + t.Name(), 1110 PartitionID: res.partitionID, 1111 SupportedCodecs: supportedCodecs, 1112 }) 1113 1114 return nil 1115 }) 1116 } else { 1117 res.stream.EXPECT().Send(&req).DoAndReturn(func(receivedRequest rawtopicwriter.ClientMessage) error { 1118 mess := receivedRequest.(*rawtopicwriter.InitRequest) 1119 options.customInitRequestHandler(res, mess) 1120 1121 return nil 1122 }) 1123 } 1124 1125 streamClosed := make(empty.Chan) 1126 res.stream.EXPECT().CloseSend().DoAndReturn(func() error { 1127 close(streamClosed) 1128 1129 return nil 1130 }) 1131 1132 res.writer.start() 1133 if !options.skipWaitInitResponse { 1134 require.NoError(t, res.writer.waitFirstInitResponse(res.ctx)) 1135 } 1136 1137 t.Cleanup(func() { 1138 close(res.stopReadEvents) 1139 _ = res.writer.close(context.Background(), errors.New("stop writer test environment")) 1140 <-streamClosed 1141 }) 1142 1143 return res 1144 } 1145 1146 func (e *testEnv) sendFromServer(msg rawtopicwriter.ServerMessage) { 1147 if msg.StatusData().Status == 0 { 1148 msg.SetStatus(rawydb.StatusSuccess) 1149 } 1150 1151 e.sendFromServerChannel <- sendFromServerResponse{msg: msg} 1152 } 1153 1154 func (e *testEnv) receiveMessageHandler() (rawtopicwriter.ServerMessage, error) { 1155 select { 1156 case <-e.stopReadEvents: 1157 return nil, fmt.Errorf("test: stop test environment") 1158 case res := <-e.sendFromServerChannel: 1159 return res.msg, res.err 1160 } 1161 } 1162 1163 type sendFromServerResponse struct { 1164 msg rawtopicwriter.ServerMessage 1165 err error 1166 } 1167 1168 func testCreateInitRequest(w *WriterReconnector) rawtopicwriter.InitRequest { 1169 req := newSingleStreamWriterStopped(context.Background(), w.createWriterStreamConfig(nil)).createInitRequest() 1170 1171 return req 1172 }