github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/topic/topicreaderinternal/stream_reader_impl_test.go (about) 1 package topicreaderinternal 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "context" 7 "errors" 8 "io" 9 "sync" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/require" 14 "go.uber.org/mock/gomock" 15 16 "github.com/ydb-platform/ydb-go-sdk/v3/internal/empty" 17 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic" 18 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopiccommon" 19 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicreader" 20 "github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawydb" 21 "github.com/ydb-platform/ydb-go-sdk/v3/internal/topic/topicreadercommon" 22 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 23 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" 24 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync" 25 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" 26 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 27 ) 28 29 func TestTopicStreamReaderImpl_BufferCounterOnStopPartition(t *testing.T) { 30 table := []struct { 31 name string 32 graceful bool 33 }{ 34 { 35 name: "graceful", 36 graceful: true, 37 }, 38 { 39 name: "force", 40 graceful: false, 41 }, 42 } 43 44 for _, test := range table { 45 t.Run(test.name, func(t *testing.T) { 46 e := newTopicReaderTestEnv(t) 47 e.Start() 48 49 initialBufferSize := e.reader.restBufferSizeBytes.Load() 50 messageSize := initialBufferSize - 1 51 52 e.stream.EXPECT().Send(&rawtopicreader.ReadRequest{BytesSize: int(messageSize)}).MaxTimes(1) 53 54 messageReaded := make(empty.Chan) 55 e.SendFromServerAndSetNextCallback(&rawtopicreader.ReadResponse{ 56 BytesSize: int(messageSize), 57 PartitionData: []rawtopicreader.PartitionData{ 58 { 59 PartitionSessionID: e.partitionSessionID, 60 Batches: []rawtopicreader.Batch{ 61 { 62 Codec: 0, 63 ProducerID: "", 64 WriteSessionMeta: nil, 65 WrittenAt: time.Time{}, 66 MessageData: []rawtopicreader.MessageData{ 67 { 68 Offset: 1, 69 SeqNo: 1, 70 }, 71 }, 72 }, 73 }, 74 }, 75 }, 76 }, func() { 77 close(messageReaded) 78 }) 79 <-messageReaded 80 require.Equal(t, int64(1), e.reader.restBufferSizeBytes.Load()) 81 82 partitionStopped := make(empty.Chan) 83 e.SendFromServerAndSetNextCallback(&rawtopicreader.StopPartitionSessionRequest{ 84 ServerMessageMetadata: rawtopiccommon.ServerMessageMetadata{}, 85 PartitionSessionID: e.partitionSessionID, 86 Graceful: test.graceful, 87 CommittedOffset: 0, 88 }, func() { 89 close(partitionStopped) 90 }) 91 <-partitionStopped 92 93 fixedBufferSizeCtx, cancel := context.WithCancel(e.ctx) 94 go func() { 95 xtest.SpinWaitCondition(t, nil, func() bool { 96 return initialBufferSize == e.reader.restBufferSizeBytes.Load() 97 }) 98 cancel() 99 }() 100 101 _, _ = e.reader.ReadMessageBatch(fixedBufferSizeCtx, newReadMessageBatchOptions()) 102 <-fixedBufferSizeCtx.Done() 103 require.Equal(t, initialBufferSize, e.reader.restBufferSizeBytes.Load()) 104 }) 105 } 106 } 107 108 func TestTopicStreamReaderImpl_CommitStolen(t *testing.T) { 109 xtest.TestManyTimesWithName(t, "SimpleCommit", func(t testing.TB) { 110 e := newTopicReaderTestEnv(t) 111 e.Start() 112 113 lastOffset := e.partitionSession.LastReceivedMessageOffset() 114 const dataSize = 4 115 116 // request new data portion 117 readRequestReceived := make(empty.Chan) 118 e.stream.EXPECT().Send( 119 &rawtopicreader.ReadRequest{BytesSize: dataSize * 2}, 120 ).DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 121 close(readRequestReceived) 122 123 return nil 124 }) 125 126 commitReceived := make(empty.Chan) 127 // Expect commit message with stole 128 e.stream.EXPECT().Send( 129 &rawtopicreader.CommitOffsetRequest{ 130 CommitOffsets: []rawtopicreader.PartitionCommitOffset{ 131 { 132 PartitionSessionID: e.partitionSessionID, 133 Offsets: []rawtopiccommon.OffsetRange{ 134 { 135 Start: lastOffset + 1, 136 End: lastOffset + 16, 137 }, 138 }, 139 }, 140 }, 141 }, 142 ).DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 143 close(commitReceived) 144 145 return nil 146 }) 147 148 // send message with stole offsets 149 // 150 e.SendFromServer(&rawtopicreader.ReadResponse{ 151 BytesSize: dataSize, 152 PartitionData: []rawtopicreader.PartitionData{ 153 { 154 PartitionSessionID: e.partitionSessionID, 155 Batches: []rawtopicreader.Batch{ 156 { 157 Codec: rawtopiccommon.CodecRaw, 158 ProducerID: "1", 159 MessageData: []rawtopicreader.MessageData{ 160 { 161 Offset: lastOffset + 10, 162 }, 163 }, 164 }, 165 }, 166 }, 167 }, 168 }) 169 170 e.SendFromServer(&rawtopicreader.ReadResponse{ 171 BytesSize: dataSize, 172 PartitionData: []rawtopicreader.PartitionData{ 173 { 174 PartitionSessionID: e.partitionSessionID, 175 Batches: []rawtopicreader.Batch{ 176 { 177 Codec: rawtopiccommon.CodecRaw, 178 ProducerID: "1", 179 MessageData: []rawtopicreader.MessageData{ 180 { 181 Offset: lastOffset + 15, 182 }, 183 }, 184 }, 185 }, 186 }, 187 }, 188 }) 189 190 opts := newReadMessageBatchOptions() 191 opts.MinCount = 2 192 batch, err := e.reader.ReadMessageBatch(e.ctx, opts) 193 require.NoError(t, err) 194 require.NoError(t, e.reader.Commit(e.ctx, topicreadercommon.GetCommitRange(batch))) 195 xtest.WaitChannelClosed(t, commitReceived) 196 xtest.WaitChannelClosed(t, readRequestReceived) 197 }) 198 xtest.TestManyTimesWithName(t, "WrongOrderCommitWithSyncMode", func(t testing.TB) { 199 e := newTopicReaderTestEnv(t) 200 e.reader.cfg.CommitMode = topicreadercommon.CommitModeSync 201 e.Start() 202 203 lastOffset := e.partitionSession.LastReceivedMessageOffset() 204 const dataSize = 4 205 // request new data portion 206 readRequestReceived := make(empty.Chan) 207 e.stream.EXPECT().Send( 208 &rawtopicreader.ReadRequest{BytesSize: dataSize * 2}, 209 ).DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 210 close(readRequestReceived) 211 212 return nil 213 }) 214 215 e.SendFromServer(&rawtopicreader.ReadResponse{ 216 BytesSize: dataSize, 217 PartitionData: []rawtopicreader.PartitionData{ 218 { 219 PartitionSessionID: e.partitionSessionID, 220 Batches: []rawtopicreader.Batch{ 221 { 222 Codec: rawtopiccommon.CodecRaw, 223 ProducerID: "1", 224 MessageData: []rawtopicreader.MessageData{ 225 { 226 Offset: lastOffset + 1, 227 }, 228 }, 229 }, 230 }, 231 }, 232 }, 233 }) 234 235 e.SendFromServer(&rawtopicreader.ReadResponse{ 236 BytesSize: dataSize, 237 PartitionData: []rawtopicreader.PartitionData{ 238 { 239 PartitionSessionID: e.partitionSessionID, 240 Batches: []rawtopicreader.Batch{ 241 { 242 Codec: rawtopiccommon.CodecRaw, 243 ProducerID: "1", 244 MessageData: []rawtopicreader.MessageData{ 245 { 246 Offset: lastOffset + 2, 247 }, 248 }, 249 }, 250 }, 251 }, 252 }, 253 }) 254 255 opts := newReadMessageBatchOptions() 256 opts.MinCount = 2 257 batch, err := e.reader.ReadMessageBatch(e.ctx, opts) 258 require.NoError(t, err) 259 require.ErrorIs(t, e.reader.Commit( 260 e.ctx, 261 topicreadercommon.GetCommitRange(batch.Messages[1]), 262 ), topicreadercommon.ErrWrongCommitOrderInSyncMode) 263 xtest.WaitChannelClosed(t, readRequestReceived) 264 }) 265 266 xtest.TestManyTimesWithName(t, "CommitAfterGracefulStopPartition", func(t testing.TB) { 267 e := newTopicReaderTestEnv(t) 268 269 committed := e.partitionSession.CommittedOffset() 270 commitReceived := make(empty.Chan) 271 e.stream.EXPECT().Send(&rawtopicreader.CommitOffsetRequest{CommitOffsets: []rawtopicreader.PartitionCommitOffset{ 272 { 273 PartitionSessionID: e.partitionSessionID, 274 Offsets: []rawtopiccommon.OffsetRange{ 275 { 276 Start: committed, 277 End: committed + 1, 278 }, 279 }, 280 }, 281 }}).DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 282 close(commitReceived) 283 284 return nil 285 }) 286 287 stopPartitionResponseSent := make(empty.Chan) 288 e.stream.EXPECT().Send(&rawtopicreader.StopPartitionSessionResponse{PartitionSessionID: e.partitionSessionID}). 289 DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 290 close(stopPartitionResponseSent) 291 292 return nil 293 }) 294 295 e.Start() 296 297 // send from server message, then partition graceful stop request 298 go func() { 299 e.SendFromServer(&rawtopicreader.ReadResponse{ 300 PartitionData: []rawtopicreader.PartitionData{ 301 { 302 PartitionSessionID: e.partitionSessionID, 303 Batches: []rawtopicreader.Batch{ 304 { 305 Codec: rawtopiccommon.CodecRaw, 306 MessageData: []rawtopicreader.MessageData{ 307 { 308 Offset: committed, 309 SeqNo: 1, 310 }, 311 }, 312 }, 313 }, 314 }, 315 }, 316 }) 317 e.SendFromServer(&rawtopicreader.StopPartitionSessionRequest{ 318 PartitionSessionID: e.partitionSessionID, 319 Graceful: true, 320 }) 321 }() 322 323 readCtx, readCtxCancel := xcontext.WithCancel(e.ctx) 324 go func() { 325 <-stopPartitionResponseSent 326 readCtxCancel() 327 }() 328 329 batch, err := e.reader.ReadMessageBatch(readCtx, newReadMessageBatchOptions()) 330 require.NoError(t, err) 331 err = e.reader.Commit(e.ctx, topicreadercommon.GetCommitRange(batch)) 332 require.NoError(t, err) 333 _, err = e.reader.ReadMessageBatch(readCtx, newReadMessageBatchOptions()) 334 require.ErrorIs(t, err, context.Canceled) 335 336 select { 337 case <-e.partitionSession.Context().Done(): 338 // pass 339 case <-time.After(time.Second): 340 t.Fatal("partition session not closed") 341 } 342 343 xtest.WaitChannelClosed(t, commitReceived) 344 }) 345 } 346 347 func TestTopicStreamReaderImpl_Create(t *testing.T) { 348 xtest.TestManyTimesWithName(t, "BadSessionInitialization", func(t testing.TB) { 349 mc := gomock.NewController(t) 350 stream := NewMockRawTopicReaderStream(mc) 351 stream.EXPECT().Send(gomock.Any()).Return(nil) 352 stream.EXPECT().Recv().Return(&rawtopicreader.StartPartitionSessionRequest{ 353 ServerMessageMetadata: rawtopiccommon.ServerMessageMetadata{Status: rawydb.StatusInternalError}, 354 }, nil) 355 stream.EXPECT().CloseSend().Return(nil) 356 357 reader, err := newTopicStreamReader(nil, topicreadercommon.NextReaderID(), stream, newTopicStreamReaderConfig()) 358 require.Error(t, err) 359 require.Nil(t, reader) 360 }) 361 } 362 363 func TestTopicStreamReaderImpl_WaitInit(t *testing.T) { 364 t.Run("OK", func(t *testing.T) { 365 e := newTopicReaderTestEnv(t) 366 e.Start() 367 err := e.reader.WaitInit(context.Background()) 368 require.NoError(t, err) 369 }) 370 371 t.Run("not started", func(t *testing.T) { 372 e := newTopicReaderTestEnv(t) 373 err := e.reader.WaitInit(context.Background()) 374 require.Error(t, err) 375 }) 376 } 377 378 func TestStreamReaderImpl_OnPartitionCloseHandle(t *testing.T) { 379 xtest.TestManyTimesWithName(t, "GracefulFalseCancelPartitionContext", func(t testing.TB) { 380 e := newTopicReaderTestEnv(t) 381 e.Start() 382 383 require.NoError(t, e.partitionSession.Context().Err()) 384 385 // stop partition 386 e.SendFromServerAndSetNextCallback( 387 &rawtopicreader.StopPartitionSessionRequest{PartitionSessionID: e.partitionSessionID}, 388 func() { 389 require.Error(t, e.partitionSession.Context().Err()) 390 }) 391 e.WaitMessageReceived() 392 }) 393 xtest.TestManyTimesWithName(t, "TraceGracefulTrue", func(t testing.TB) { 394 e := newTopicReaderTestEnv(t) 395 396 readMessagesCtx, readMessagesCtxCancel := xcontext.WithCancel(context.Background()) 397 committedOffset := int64(222) 398 399 e.reader.cfg.Trace.OnReaderPartitionReadStopResponse = func(info trace.TopicReaderPartitionReadStopResponseStartInfo) func(doneInfo trace.TopicReaderPartitionReadStopResponseDoneInfo) { //nolint:lll 400 expected := trace.TopicReaderPartitionReadStopResponseStartInfo{ 401 ReaderConnectionID: e.reader.readConnectionID, 402 PartitionContext: e.partitionSession.Context(), 403 Topic: e.partitionSession.Topic, 404 PartitionID: e.partitionSession.PartitionID, 405 PartitionSessionID: e.partitionSession.StreamPartitionSessionID.ToInt64(), 406 CommittedOffset: committedOffset, 407 Graceful: true, 408 } 409 require.Equal(t, expected, info) 410 411 require.NoError(t, info.PartitionContext.Err()) 412 413 readMessagesCtxCancel() 414 415 return nil 416 } 417 418 e.Start() 419 420 stopPartitionResponseSent := make(empty.Chan) 421 e.stream.EXPECT().Send(&rawtopicreader.StopPartitionSessionResponse{ 422 PartitionSessionID: e.partitionSessionID, 423 }).DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 424 close(stopPartitionResponseSent) 425 426 return nil 427 }) 428 429 e.SendFromServer(&rawtopicreader.StopPartitionSessionRequest{ 430 PartitionSessionID: e.partitionSessionID, 431 Graceful: true, 432 CommittedOffset: rawtopiccommon.NewOffset(committedOffset), 433 }) 434 435 _, err := e.reader.ReadMessageBatch(readMessagesCtx, newReadMessageBatchOptions()) 436 require.Error(t, err) 437 require.Error(t, readMessagesCtx.Err()) 438 xtest.WaitChannelClosed(t, stopPartitionResponseSent) 439 }) 440 xtest.TestManyTimesWithName(t, "TraceGracefulFalse", func(t testing.TB) { 441 e := newTopicReaderTestEnv(t) 442 443 readMessagesCtx, readMessagesCtxCancel := xcontext.WithCancel(context.Background()) 444 committedOffset := int64(222) 445 446 e.reader.cfg.Trace.OnReaderPartitionReadStopResponse = func(info trace.TopicReaderPartitionReadStopResponseStartInfo) func(doneInfo trace.TopicReaderPartitionReadStopResponseDoneInfo) { //nolint:lll 447 expected := trace.TopicReaderPartitionReadStopResponseStartInfo{ 448 ReaderConnectionID: e.reader.readConnectionID, 449 PartitionContext: e.partitionSession.Context(), 450 Topic: e.partitionSession.Topic, 451 PartitionID: e.partitionSession.PartitionID, 452 PartitionSessionID: e.partitionSession.StreamPartitionSessionID.ToInt64(), 453 CommittedOffset: committedOffset, 454 Graceful: false, 455 } 456 require.Equal(t, expected, info) 457 require.Error(t, info.PartitionContext.Err()) 458 459 readMessagesCtxCancel() 460 461 return nil 462 } 463 464 e.Start() 465 466 e.SendFromServer(&rawtopicreader.StopPartitionSessionRequest{ 467 PartitionSessionID: e.partitionSessionID, 468 Graceful: false, 469 CommittedOffset: rawtopiccommon.NewOffset(committedOffset), 470 }) 471 472 _, err := e.reader.ReadMessageBatch(readMessagesCtx, newReadMessageBatchOptions()) 473 require.Error(t, err) 474 require.Error(t, readMessagesCtx.Err()) 475 }) 476 } 477 478 func TestTopicStreamReaderImpl_ReadMessages(t *testing.T) { 479 t.Run("BufferSize", func(t *testing.T) { 480 waitChangeRestBufferSizeBytes := func(r *topicStreamReaderImpl, old int64) { 481 xtest.SpinWaitCondition(t, nil, func() bool { 482 return r.restBufferSizeBytes.Load() != old 483 }) 484 } 485 486 xtest.TestManyTimesWithName(t, "InitialBufferSize", func(t testing.TB) { 487 e := newTopicReaderTestEnv(t) 488 e.Start() 489 waitChangeRestBufferSizeBytes(e.reader, 0) 490 require.Equal(t, e.initialBufferSizeBytes, e.reader.restBufferSizeBytes.Load()) 491 }) 492 493 xtest.TestManyTimesWithName(t, "DecrementIncrementBufferSize", func(t testing.TB) { 494 e := newTopicReaderTestEnv(t) 495 496 // doesn't check sends 497 e.stream.EXPECT().Send(gomock.Any()).Return(nil).MinTimes(1) 498 499 e.Start() 500 waitChangeRestBufferSizeBytes(e.reader, 0) 501 502 const dataSize = 1000 503 e.SendFromServer(&rawtopicreader.ReadResponse{BytesSize: dataSize, PartitionData: []rawtopicreader.PartitionData{ 504 { 505 PartitionSessionID: e.partitionSessionID, 506 Batches: []rawtopicreader.Batch{ 507 { 508 MessageData: []rawtopicreader.MessageData{ 509 { 510 Offset: 1, 511 SeqNo: 1, 512 Data: []byte{1, 2}, 513 }, 514 { 515 Offset: 2, 516 SeqNo: 2, 517 Data: []byte{4, 5, 6}, 518 }, 519 { 520 Offset: 3, 521 SeqNo: 3, 522 Data: []byte{7}, 523 }, 524 }, 525 }, 526 }, 527 }, 528 }}) 529 waitChangeRestBufferSizeBytes(e.reader, e.initialBufferSizeBytes) 530 expectedBufferSizeAfterReceiveMessages := e.initialBufferSizeBytes - dataSize 531 require.Equal(t, expectedBufferSizeAfterReceiveMessages, e.reader.restBufferSizeBytes.Load()) 532 533 oneOption := newReadMessageBatchOptions() 534 oneOption.MaxCount = 1 535 _, err := e.reader.ReadMessageBatch(e.ctx, oneOption) 536 require.NoError(t, err) 537 538 waitChangeRestBufferSizeBytes(e.reader, expectedBufferSizeAfterReceiveMessages) 539 540 bufferSizeAfterReadOneMessage := e.reader.restBufferSizeBytes.Load() 541 542 _, err = e.reader.ReadMessageBatch(e.ctx, newReadMessageBatchOptions()) 543 require.NoError(t, err) 544 545 waitChangeRestBufferSizeBytes(e.reader, bufferSizeAfterReadOneMessage) 546 require.Equal(t, e.initialBufferSizeBytes, e.reader.restBufferSizeBytes.Load()) 547 }) 548 549 xtest.TestManyTimesWithName(t, "ForceReturnBatchIfBufferFull", func(t testing.TB) { 550 e := newTopicReaderTestEnv(t) 551 552 dataRequested := make(empty.Chan) 553 e.stream.EXPECT().Send( 554 &rawtopicreader.ReadRequest{BytesSize: int(e.initialBufferSizeBytes)}, 555 ). 556 DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 557 close(dataRequested) 558 559 return nil 560 }) 561 562 e.Start() 563 waitChangeRestBufferSizeBytes(e.reader, 0) 564 565 e.SendFromServer(&rawtopicreader.ReadResponse{ 566 BytesSize: int(e.initialBufferSizeBytes), 567 PartitionData: []rawtopicreader.PartitionData{ 568 { 569 PartitionSessionID: e.partitionSessionID, 570 Batches: []rawtopicreader.Batch{ 571 { 572 MessageData: []rawtopicreader.MessageData{ 573 { 574 Offset: 1, 575 SeqNo: 1, 576 Data: []byte{1, 2, 3}, 577 }, 578 }, 579 }, 580 }, 581 }, 582 }, 583 }) 584 needReadTwoMessages := newReadMessageBatchOptions() 585 needReadTwoMessages.MinCount = 2 586 587 readTimeoutCtx, cancel := xcontext.WithTimeout(e.ctx, time.Second) 588 defer cancel() 589 590 batch, err := e.reader.ReadMessageBatch(readTimeoutCtx, needReadTwoMessages) 591 require.NoError(t, err) 592 require.Len(t, batch.Messages, 1) 593 594 <-dataRequested 595 }) 596 }) 597 598 xtest.TestManyTimesWithName(t, "ReadBatch", func(t testing.TB) { 599 e := newTopicReaderTestEnv(t) 600 e.Start() 601 602 compress := func(msg string) []byte { 603 b := &bytes.Buffer{} 604 writer := gzip.NewWriter(b) 605 _, err := writer.Write([]byte(msg)) 606 require.NoError(t, writer.Close()) 607 require.NoError(t, err) 608 609 return b.Bytes() 610 } 611 612 prevOffset := e.partitionSession.LastReceivedMessageOffset() 613 614 sendDataRequestCompleted := make(empty.Chan) 615 dataSize := 6 616 e.stream.EXPECT().Send( 617 &rawtopicreader.ReadRequest{BytesSize: dataSize}, 618 ).DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 619 close(sendDataRequestCompleted) 620 621 return nil 622 }) 623 e.SendFromServer(&rawtopicreader.ReadResponse{ 624 BytesSize: dataSize, 625 PartitionData: []rawtopicreader.PartitionData{ 626 { 627 PartitionSessionID: e.partitionSessionID, 628 Batches: []rawtopicreader.Batch{ 629 { 630 Codec: rawtopiccommon.CodecRaw, 631 WriteSessionMeta: map[string]string{"a": "b", "c": "d"}, 632 WrittenAt: testTime(5), 633 MessageData: []rawtopicreader.MessageData{ 634 { 635 Offset: prevOffset + 1, 636 SeqNo: 1, 637 CreatedAt: testTime(1), 638 Data: []byte("123"), 639 UncompressedSize: 3, 640 MessageGroupID: "1", 641 }, 642 { 643 Offset: prevOffset + 2, 644 SeqNo: 2, 645 CreatedAt: testTime(2), 646 Data: []byte("4567"), 647 UncompressedSize: 4, 648 MessageGroupID: "1", 649 }, 650 }, 651 }, 652 { 653 Codec: rawtopiccommon.CodecGzip, 654 WriteSessionMeta: map[string]string{"e": "f", "g": "h"}, 655 WrittenAt: testTime(6), 656 MessageData: []rawtopicreader.MessageData{ 657 { 658 Offset: prevOffset + 10, 659 SeqNo: 3, 660 CreatedAt: testTime(3), 661 Data: compress("098"), 662 UncompressedSize: 3, 663 MessageGroupID: "2", 664 }, 665 { 666 Offset: prevOffset + 20, 667 SeqNo: 4, 668 CreatedAt: testTime(4), 669 Data: compress("0987"), 670 UncompressedSize: 4, 671 MessageGroupID: "2", 672 }, 673 }, 674 }, 675 { 676 Codec: rawtopiccommon.CodecRaw, 677 WriteSessionMeta: map[string]string{"a": "b", "c": "d"}, 678 WrittenAt: testTime(7), 679 MessageData: []rawtopicreader.MessageData{ 680 { 681 Offset: prevOffset + 30, 682 SeqNo: 5, 683 CreatedAt: testTime(5), 684 Data: []byte("test"), 685 UncompressedSize: 4, 686 MessageGroupID: "1", 687 MetadataItems: []rawtopiccommon.MetadataItem{ 688 { 689 Key: "first", 690 Value: []byte("first-value"), 691 }, 692 { 693 Key: "second", 694 Value: []byte("second-value"), 695 }, 696 }, 697 }, 698 { 699 Offset: prevOffset + 31, 700 SeqNo: 6, 701 CreatedAt: testTime(5), 702 Data: []byte("4567"), 703 UncompressedSize: 4, 704 MessageGroupID: "1", 705 MetadataItems: []rawtopiccommon.MetadataItem{ 706 { 707 Key: "doubled-key", 708 Value: []byte("bad"), 709 }, 710 { 711 Key: "doubled-key", 712 Value: []byte("good"), 713 }, 714 }, 715 }, 716 }, 717 }, 718 }, 719 }, 720 }, 721 }, 722 ) 723 724 expectedData := [][]byte{[]byte("123"), []byte("4567"), []byte("098"), []byte("0987"), []byte("test"), []byte("4567")} 725 expectedBatch := topicreadercommon.BatchSetCommitRangeForTest(&topicreadercommon.PublicBatch{ 726 Messages: []*topicreadercommon.PublicMessage{ 727 topicreadercommon.NewPublicMessageBuilder(). 728 Seqno(1). 729 CreatedAt(testTime(1)). 730 MessageGroupID("1"). 731 Offset(prevOffset.ToInt64() + 1). 732 WrittenAt(testTime(5)). 733 WriteSessionMetadata(map[string]string{"a": "b", "c": "d"}). 734 UncompressedSize(3). 735 RawDataLen(3). 736 CommitRange(topicreadercommon.CommitRange{ 737 CommitOffsetStart: prevOffset + 1, 738 CommitOffsetEnd: prevOffset + 2, 739 PartitionSession: e.partitionSession, 740 }).Build(), 741 topicreadercommon.NewPublicMessageBuilder(). 742 Seqno(2). 743 CreatedAt(testTime(2)). 744 MessageGroupID("1"). 745 Offset(prevOffset.ToInt64() + 2). 746 WrittenAt(testTime(5)). 747 WriteSessionMetadata(map[string]string{"a": "b", "c": "d"}). 748 UncompressedSize(4). 749 RawDataLen(4). 750 CommitRange(topicreadercommon.CommitRange{ 751 CommitOffsetStart: prevOffset + 2, 752 CommitOffsetEnd: prevOffset + 3, 753 PartitionSession: e.partitionSession, 754 }).Build(), 755 topicreadercommon.NewPublicMessageBuilder(). 756 Seqno(3). 757 CreatedAt(testTime(3)). 758 MessageGroupID("2"). 759 Offset(prevOffset.ToInt64() + 10). 760 WrittenAt(testTime(6)). 761 WriteSessionMetadata(map[string]string{"e": "f", "g": "h"}). 762 UncompressedSize(3). 763 RawDataLen(len(compress("098"))). 764 CommitRange(topicreadercommon.CommitRange{ 765 CommitOffsetStart: prevOffset + 3, 766 CommitOffsetEnd: prevOffset + 11, 767 PartitionSession: e.partitionSession, 768 }).Build(), 769 topicreadercommon.NewPublicMessageBuilder(). 770 Seqno(4). 771 CreatedAt(testTime(4)). 772 MessageGroupID("2"). 773 Offset(prevOffset.ToInt64() + 20). 774 WrittenAt(testTime(6)). 775 WriteSessionMetadata(map[string]string{"e": "f", "g": "h"}). 776 UncompressedSize(4). 777 RawDataLen(len(compress("0987"))). 778 CommitRange(topicreadercommon.CommitRange{ 779 CommitOffsetStart: prevOffset + 11, 780 CommitOffsetEnd: prevOffset + 21, 781 PartitionSession: e.partitionSession, 782 }).Build(), 783 topicreadercommon.NewPublicMessageBuilder(). 784 Seqno(5). 785 CreatedAt(testTime(5)). 786 MessageGroupID("1"). 787 Metadata(map[string][]byte{ 788 "first": []byte("first-value"), 789 "second": []byte("second-value"), 790 }). 791 Offset(prevOffset.ToInt64() + 30). 792 WrittenAt(testTime(7)). 793 WriteSessionMetadata(map[string]string{"a": "b", "c": "d"}). 794 UncompressedSize(4). 795 RawDataLen(4). 796 CommitRange(topicreadercommon.CommitRange{ 797 CommitOffsetStart: prevOffset + 21, 798 CommitOffsetEnd: prevOffset + 31, 799 PartitionSession: e.partitionSession, 800 }).Build(), 801 topicreadercommon.NewPublicMessageBuilder(). 802 Seqno(6). 803 CreatedAt(testTime(5)). 804 MessageGroupID("1"). 805 Metadata(map[string][]byte{ 806 "doubled-key": []byte("good"), 807 }). 808 Offset(prevOffset.ToInt64() + 31). 809 WrittenAt(testTime(7)). 810 WriteSessionMetadata(map[string]string{"a": "b", "c": "d"}). 811 UncompressedSize(4). 812 RawDataLen(4). 813 CommitRange(topicreadercommon.CommitRange{ 814 CommitOffsetStart: prevOffset + 31, 815 CommitOffsetEnd: prevOffset + 32, 816 PartitionSession: e.partitionSession, 817 }).Build(), 818 }, 819 }, topicreadercommon.CommitRange{ 820 CommitOffsetStart: prevOffset + 1, 821 CommitOffsetEnd: prevOffset + 32, 822 PartitionSession: e.partitionSession, 823 }) 824 825 opts := newReadMessageBatchOptions() 826 opts.MinCount = 6 827 batch, err := e.reader.ReadMessageBatch(e.ctx, opts) 828 require.NoError(t, err) 829 830 data := make([][]byte, 0, len(batch.Messages)) 831 for i := range batch.Messages { 832 content, err := io.ReadAll(batch.Messages[i]) 833 require.NoError(t, err) 834 data = append(data, content) 835 topicreadercommon.MessageSetNilDataForTest(batch.Messages[i]) 836 } 837 838 require.Equal(t, expectedData, data) 839 require.Equal(t, expectedBatch, batch) 840 <-sendDataRequestCompleted 841 }) 842 } 843 844 func TestTopicStreamReadImpl_BatchReaderWantMoreMessagesThenBufferCanHold(t *testing.T) { 845 sendMessageWithFullBuffer := func(e *streamEnv) empty.Chan { 846 nextDataRequested := make(empty.Chan) 847 e.stream.EXPECT().Send( 848 &rawtopicreader.ReadRequest{BytesSize: int(e.initialBufferSizeBytes)}, 849 ).DoAndReturn(func(_ rawtopicreader.ClientMessage) error { 850 close(nextDataRequested) 851 852 return nil 853 }) 854 855 e.SendFromServer( 856 &rawtopicreader.ReadResponse{ 857 BytesSize: int(e.initialBufferSizeBytes), 858 PartitionData: []rawtopicreader.PartitionData{ 859 { 860 PartitionSessionID: e.partitionSessionID, 861 Batches: []rawtopicreader.Batch{ 862 { 863 Codec: rawtopiccommon.CodecRaw, 864 MessageData: []rawtopicreader.MessageData{ 865 { 866 Offset: 1, 867 }, 868 }, 869 }, 870 }, 871 }, 872 }, 873 }) 874 875 return nextDataRequested 876 } 877 878 xtest.TestManyTimesWithName(t, "ReadAfterMessageInBuffer", func(t testing.TB) { 879 e := newTopicReaderTestEnv(t) 880 e.Start() 881 882 nextDataRequested := sendMessageWithFullBuffer(&e) 883 884 // wait message received to internal buffer 885 xtest.SpinWaitCondition(t, &e.reader.batcher.m, func() bool { 886 return len(e.reader.batcher.messages) > 0 887 }) 888 889 xtest.SpinWaitCondition(t, nil, func() bool { 890 return e.reader.restBufferSizeBytes.Load() == 0 891 }) 892 893 opts := newReadMessageBatchOptions() 894 opts.MinCount = 2 895 896 readCtx, cancel := xcontext.WithTimeout(e.ctx, time.Second) 897 defer cancel() 898 batch, err := e.reader.ReadMessageBatch(readCtx, opts) 899 require.NoError(t, err) 900 require.Len(t, batch.Messages, 1) 901 require.Equal(t, int64(1), batch.Messages[0].Offset) 902 903 <-nextDataRequested 904 require.Equal(t, e.initialBufferSizeBytes, e.reader.restBufferSizeBytes.Load()) 905 }) 906 907 xtest.TestManyTimesWithName(t, "ReadBeforeMessageInBuffer", func(t testing.TB) { 908 e := newTopicReaderTestEnv(t) 909 e.Start() 910 911 readCompleted := make(empty.Chan) 912 var batch *topicreadercommon.PublicBatch 913 var readErr error 914 go func() { 915 defer close(readCompleted) 916 917 opts := newReadMessageBatchOptions() 918 opts.MinCount = 2 919 920 readCtx, cancel := xcontext.WithTimeout(e.ctx, time.Second) 921 defer cancel() 922 batch, readErr = e.reader.ReadMessageBatch(readCtx, opts) 923 }() 924 925 // wait to start pop 926 e.reader.batcher.notifyAboutNewMessages() 927 xtest.SpinWaitCondition(t, &e.reader.batcher.m, func() bool { 928 return len(e.reader.batcher.hasNewMessages) == 0 929 }) 930 931 nextDataRequested := sendMessageWithFullBuffer(&e) 932 933 <-readCompleted 934 require.NoError(t, readErr) 935 require.Len(t, batch.Messages, 1) 936 require.Equal(t, int64(1), batch.Messages[0].Offset) 937 938 <-nextDataRequested 939 require.Equal(t, e.initialBufferSizeBytes, e.reader.restBufferSizeBytes.Load()) 940 }) 941 } 942 943 func TestTopicStreamReadImpl_CommitWithBadSession(t *testing.T) { 944 commitByMode := func(mode topicreadercommon.PublicCommitMode) error { 945 sleep := func() { 946 time.Sleep(time.Second / 10) 947 } 948 e := newTopicReaderTestEnv(t) 949 e.reader.cfg.CommitMode = mode 950 e.Start() 951 952 cr := topicreadercommon.CommitRange{ 953 PartitionSession: topicreadercommon.NewPartitionSession( 954 context.Background(), 955 "asd", 956 123, 957 topicreadercommon.NextReaderID(), 958 "bad-connection-id", 959 222, 960 322, 961 213, 962 ), 963 } 964 commitErr := e.reader.Commit(e.ctx, cr) 965 966 sleep() 967 968 require.False(t, e.reader.closed) 969 970 return commitErr 971 } 972 t.Run("CommitModeNone", func(t *testing.T) { 973 require.ErrorIs( 974 t, 975 commitByMode(topicreadercommon.CommitModeNone), 976 topicreadercommon.ErrCommitDisabled, 977 ) 978 }) 979 t.Run("CommitModeSync", func(t *testing.T) { 980 require.ErrorIs( 981 t, 982 commitByMode(topicreadercommon.CommitModeSync), 983 topicreadercommon.PublicErrCommitSessionToExpiredSession, 984 ) 985 }) 986 t.Run("CommitModeAsync", func(t *testing.T) { 987 require.NoError(t, commitByMode(topicreadercommon.CommitModeAsync)) 988 }) 989 } 990 991 type streamEnv struct { 992 TopicClient *MockTopicClient 993 ctx context.Context //nolint:containedctx 994 t testing.TB 995 reader *topicStreamReaderImpl 996 stopReadEvents empty.Chan 997 stopReadEventsCloseOnce sync.Once 998 stream *MockRawTopicReaderStream 999 partitionSessionID partitionSessionID 1000 mc *gomock.Controller 1001 partitionSession *topicreadercommon.PartitionSession 1002 initialBufferSizeBytes int64 1003 1004 m xsync.Mutex 1005 messagesFromServerToClient chan testStreamResult 1006 nextMessageNeedCallback func() 1007 } 1008 1009 type testStreamResult struct { 1010 nextMessageCallback func() 1011 msg rawtopicreader.ServerMessage 1012 err error 1013 waitOnly bool 1014 } 1015 1016 func newTopicReaderTestEnv(t testing.TB) streamEnv { 1017 ctx := xtest.Context(t) 1018 1019 mc := gomock.NewController(t) 1020 1021 stream := NewMockRawTopicReaderStream(mc) 1022 1023 const initialBufferSizeBytes = 1000000 1024 1025 cfg := newTopicStreamReaderConfig() 1026 cfg.BaseContext = ctx 1027 cfg.BufferSizeProtoBytes = initialBufferSizeBytes 1028 cfg.CommitterBatchTimeLag = 0 1029 1030 topicClientMock := NewMockTopicClient(mc) 1031 reader := newTopicStreamReaderStopped(topicClientMock, topicreadercommon.NextReaderID(), stream, cfg) 1032 // reader.initSession() - skip stream level initialization 1033 1034 const testPartitionID = 5 1035 const testSessionID = 15 1036 const testClientSessionID = 115 1037 const testSessionComitted = 20 1038 1039 session := topicreadercommon.NewPartitionSession( 1040 ctx, 1041 "/test", 1042 testPartitionID, 1043 reader.readerID, 1044 reader.readConnectionID, 1045 testSessionID, 1046 testClientSessionID, 1047 testSessionComitted, 1048 ) 1049 require.NoError(t, reader.sessionController.Add(session)) 1050 1051 env := streamEnv{ 1052 TopicClient: topicClientMock, 1053 ctx: ctx, 1054 t: t, 1055 initialBufferSizeBytes: initialBufferSizeBytes, 1056 reader: reader, 1057 stopReadEvents: make(empty.Chan), 1058 stream: stream, 1059 messagesFromServerToClient: make(chan testStreamResult), 1060 partitionSession: session, 1061 partitionSessionID: session.StreamPartitionSessionID, 1062 mc: mc, 1063 } 1064 1065 stream.EXPECT().Recv().AnyTimes().DoAndReturn(env.receiveMessageHandler) 1066 1067 // initial data request 1068 stream.EXPECT().Send(&rawtopicreader.ReadRequest{BytesSize: initialBufferSizeBytes}).MaxTimes(1) 1069 1070 // allow in test send data without explicit sizes 1071 stream.EXPECT().Send(&rawtopicreader.ReadRequest{BytesSize: 0}).AnyTimes() 1072 1073 streamClosed := make(empty.Chan) 1074 stream.EXPECT().CloseSend().Return(nil).DoAndReturn(func() error { 1075 close(streamClosed) 1076 env.closeStream() 1077 1078 return nil 1079 }) 1080 1081 t.Cleanup(func() { 1082 env.closeStream() 1083 _ = env.reader.CloseWithError(ctx, errors.New("test finished")) 1084 xtest.WaitChannelClosed(t, streamClosed) 1085 }) 1086 1087 t.Cleanup(func() { 1088 if messLen := len(env.messagesFromServerToClient); messLen != 0 { 1089 t.Fatalf("not all messages consumed from server: %v", messLen) 1090 } 1091 }) 1092 1093 //nolint:govet 1094 return env 1095 } 1096 1097 func (e *streamEnv) Start() { 1098 require.NoError(e.t, e.reader.startBackgroundWorkers()) 1099 xtest.SpinWaitCondition(e.t, nil, func() bool { 1100 return e.reader.restBufferSizeBytes.Load() == e.initialBufferSizeBytes 1101 }) 1102 } 1103 1104 func (e *streamEnv) readerReceiveWaitClose(callback func()) { 1105 e.stream.EXPECT().Recv().DoAndReturn(func() (rawtopicreader.ServerMessage, error) { 1106 if callback != nil { 1107 callback() 1108 } 1109 <-e.ctx.Done() 1110 1111 return nil, errors.New("test reader closed") 1112 }) 1113 } 1114 1115 func (e *streamEnv) SendFromServer(msg rawtopicreader.ServerMessage) { 1116 e.SendFromServerAndSetNextCallback(msg, nil) 1117 } 1118 1119 func (e *streamEnv) SendFromServerAndSetNextCallback(msg rawtopicreader.ServerMessage, callback func()) { 1120 if msg.StatusData().Status == 0 { 1121 msg.SetStatus(rawydb.StatusSuccess) 1122 } 1123 e.messagesFromServerToClient <- testStreamResult{msg: msg, nextMessageCallback: callback} 1124 } 1125 1126 func (e *streamEnv) WaitMessageReceived() { 1127 e.messagesFromServerToClient <- testStreamResult{waitOnly: true} 1128 } 1129 1130 func (e *streamEnv) receiveMessageHandler() (rawtopicreader.ServerMessage, error) { 1131 if e.ctx.Err() != nil { 1132 return nil, e.ctx.Err() 1133 } 1134 1135 var callback func() 1136 e.m.WithLock(func() { 1137 callback = e.nextMessageNeedCallback 1138 e.nextMessageNeedCallback = nil 1139 }) 1140 1141 if callback != nil { 1142 callback() 1143 } 1144 1145 readMessages: 1146 for { 1147 select { 1148 case <-e.ctx.Done(): 1149 return nil, e.ctx.Err() 1150 case <-e.stopReadEvents: 1151 return nil, xerrors.Wrap(errors.New("mock reader closed")) 1152 case res := <-e.messagesFromServerToClient: 1153 if res.waitOnly { 1154 continue readMessages 1155 } 1156 e.m.WithLock(func() { 1157 e.nextMessageNeedCallback = res.nextMessageCallback 1158 }) 1159 1160 return res.msg, res.err 1161 } 1162 } 1163 } 1164 1165 func (e *streamEnv) closeStream() { 1166 e.stopReadEventsCloseOnce.Do(func() { 1167 close(e.stopReadEvents) 1168 }) 1169 } 1170 1171 func TestUpdateCommitInTransaction(t *testing.T) { 1172 t.Run("OK", func(t *testing.T) { 1173 e := newTopicReaderTestEnv(t) 1174 e.Start() 1175 1176 initialCommitOffset := e.partitionSession.CommittedOffset() 1177 txID := "test-tx-id" 1178 sessionID := "test-session-id" 1179 1180 e.TopicClient.EXPECT().UpdateOffsetsInTransaction(gomock.Any(), &rawtopic.UpdateOffsetsInTransactionRequest{ 1181 OperationParams: rawydb.OperationParams{ 1182 OperationMode: rawydb.OperationParamsModeSync, 1183 }, 1184 Tx: rawtopiccommon.TransactionIdentity{ 1185 ID: txID, 1186 Session: sessionID, 1187 }, 1188 Topics: []rawtopic.UpdateOffsetsInTransactionRequest_TopicOffsets{ 1189 { 1190 Path: e.partitionSession.Topic, 1191 Partitions: []rawtopic.UpdateOffsetsInTransactionRequest_PartitionOffsets{ 1192 { 1193 PartitionID: e.partitionSession.PartitionID, 1194 PartitionOffsets: []rawtopiccommon.OffsetRange{ 1195 { 1196 Start: initialCommitOffset, 1197 End: initialCommitOffset + 1, 1198 }, 1199 }, 1200 }, 1201 }, 1202 }, 1203 }, 1204 Consumer: e.reader.cfg.Consumer, 1205 }) 1206 1207 txMock := newMockTransactionWrapper(sessionID, txID) 1208 1209 batch, err := topicreadercommon.NewBatch(e.partitionSession, []*topicreadercommon.PublicMessage{ 1210 topicreadercommon.NewPublicMessageBuilder(). 1211 Offset(e.partitionSession.CommittedOffset().ToInt64()). 1212 PartitionSession(e.partitionSession). 1213 Build(), 1214 }) 1215 require.NoError(t, err) 1216 err = e.reader.commitWithTransaction(e.ctx, txMock, batch) 1217 require.NoError(t, err) 1218 1219 require.Len(t, txMock.onCompleted, 1) 1220 txMock.onCompleted[0](nil) 1221 require.True(t, txMock.materialized) 1222 require.Equal(t, initialCommitOffset+1, e.partitionSession.CommittedOffset()) 1223 }) 1224 t.Run("FailedAddCommitToTransactions", func(t *testing.T) { 1225 e := newTopicReaderTestEnv(t) 1226 e.Start() 1227 1228 txID := "test-tx-id" 1229 sessionID := "test-session-id" 1230 1231 testError := errors.New("test error") 1232 e.TopicClient.EXPECT().UpdateOffsetsInTransaction(gomock.Any(), gomock.Any()).Return(testError) 1233 1234 txMock := newMockTransactionWrapper(sessionID, txID) 1235 1236 batch, err := topicreadercommon.NewBatch(e.partitionSession, []*topicreadercommon.PublicMessage{ 1237 topicreadercommon.NewPublicMessageBuilder(). 1238 Offset(e.partitionSession.CommittedOffset().ToInt64()). 1239 PartitionSession(e.partitionSession). 1240 Build(), 1241 }) 1242 require.NoError(t, err) 1243 err = e.reader.commitWithTransaction(e.ctx, txMock, batch) 1244 require.ErrorIs(t, err, testError) 1245 require.NoError(t, xerrors.RetryableError(err)) 1246 require.Empty(t, txMock.onCompleted) 1247 1248 require.True(t, e.reader.closed) 1249 require.ErrorIs(t, e.reader.err, testError) 1250 require.Error(t, xerrors.RetryableError(e.reader.err)) 1251 require.True(t, txMock.RolledBack) 1252 require.True(t, txMock.materialized) 1253 }) 1254 }