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  }