github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/topic/topicreadercommon/committer_test.go (about)

     1  package topicreadercommon
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/jonboulle/clockwork"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/background"
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/empty"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopiccommon"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/grpcwrapper/rawtopic/rawtopicreader"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest"
    18  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    19  )
    20  
    21  func TestCommitterCommit(t *testing.T) {
    22  	t.Run("CommitWithCancelledContext", func(t *testing.T) {
    23  		ctx := xtest.Context(t)
    24  		c := newTestCommitter(ctx, t)
    25  		c.send = func(msg rawtopicreader.ClientMessage) error {
    26  			t.Fatalf("must not call")
    27  
    28  			return nil
    29  		}
    30  
    31  		ctx, cancel := xcontext.WithCancel(ctx)
    32  		cancel()
    33  
    34  		err := c.Commit(ctx, CommitRange{})
    35  		require.ErrorIs(t, err, context.Canceled)
    36  	})
    37  }
    38  
    39  func TestCommitterCommitDisabled(t *testing.T) {
    40  	ctx := xtest.Context(t)
    41  	c := &Committer{mode: CommitModeNone}
    42  	err := c.Commit(ctx, CommitRange{})
    43  	require.ErrorIs(t, err, ErrCommitDisabled)
    44  }
    45  
    46  func TestCommitterCommitAsync(t *testing.T) {
    47  	t.Run("SendCommit", func(t *testing.T) {
    48  		ctx := xtest.Context(t)
    49  		session := newTestPartitionSession(context.Background(), 1)
    50  
    51  		cRange := CommitRange{
    52  			CommitOffsetStart: 1,
    53  			CommitOffsetEnd:   2,
    54  			PartitionSession:  session,
    55  		}
    56  
    57  		sendCalled := make(empty.Chan)
    58  		c := newTestCommitter(ctx, t)
    59  		c.mode = CommitModeAsync
    60  		c.send = func(msg rawtopicreader.ClientMessage) error {
    61  			close(sendCalled)
    62  			require.Equal(t,
    63  				&rawtopicreader.CommitOffsetRequest{
    64  					CommitOffsets: testNewCommitRanges(&cRange).ToPartitionsOffsets(),
    65  				},
    66  				msg)
    67  
    68  			return nil
    69  		}
    70  		require.NoError(t, c.Commit(ctx, cRange))
    71  		<-sendCalled
    72  	})
    73  }
    74  
    75  func TestCommitterCommitSync(t *testing.T) {
    76  	t.Run("SendCommit", func(t *testing.T) {
    77  		ctx := xtest.Context(t)
    78  		session := newTestPartitionSession(context.Background(), 1)
    79  
    80  		cRange := CommitRange{
    81  			CommitOffsetStart: 1,
    82  			CommitOffsetEnd:   2,
    83  			PartitionSession:  session,
    84  		}
    85  
    86  		sendCalled := false
    87  		c := newTestCommitter(ctx, t)
    88  		c.mode = CommitModeSync
    89  		c.send = func(msg rawtopicreader.ClientMessage) error {
    90  			sendCalled = true
    91  			require.Equal(t,
    92  				&rawtopicreader.CommitOffsetRequest{
    93  					CommitOffsets: testNewCommitRanges(&cRange).ToPartitionsOffsets(),
    94  				},
    95  				msg)
    96  			c.OnCommitNotify(session, cRange.CommitOffsetEnd)
    97  
    98  			return nil
    99  		}
   100  		require.NoError(t, c.Commit(ctx, cRange))
   101  		require.True(t, sendCalled)
   102  	})
   103  
   104  	xtest.TestManyTimesWithName(t, "SuccessCommitWithNotifyAfterCommit", func(t testing.TB) {
   105  		ctx := xtest.Context(t)
   106  		session := newTestPartitionSession(context.Background(), 1)
   107  
   108  		cRange := CommitRange{
   109  			CommitOffsetStart: 1,
   110  			CommitOffsetEnd:   2,
   111  			PartitionSession:  session,
   112  		}
   113  
   114  		commitSended := make(empty.Chan)
   115  		c := newTestCommitter(ctx, t)
   116  		c.mode = CommitModeSync
   117  		c.send = func(msg rawtopicreader.ClientMessage) error {
   118  			close(commitSended)
   119  
   120  			return nil
   121  		}
   122  
   123  		commitCompleted := make(empty.Chan)
   124  		go func() {
   125  			require.NoError(t, c.Commit(ctx, cRange))
   126  			close(commitCompleted)
   127  		}()
   128  
   129  		notifySended := false
   130  		go func() {
   131  			<-commitSended
   132  			notifySended = true
   133  			c.OnCommitNotify(session, rawtopiccommon.Offset(2))
   134  		}()
   135  
   136  		<-commitCompleted
   137  		require.True(t, notifySended)
   138  	})
   139  
   140  	t.Run("SuccessCommitPreviousCommitted", func(t *testing.T) {
   141  		ctx := xtest.Context(t)
   142  		session := newTestPartitionSession(context.Background(), 1)
   143  		session.SetCommittedOffsetForward(2)
   144  
   145  		cRange := CommitRange{
   146  			CommitOffsetStart: 1,
   147  			CommitOffsetEnd:   2,
   148  			PartitionSession:  session,
   149  		}
   150  
   151  		c := newTestCommitter(ctx, t)
   152  		require.NoError(t, c.Commit(ctx, cRange))
   153  	})
   154  
   155  	xtest.TestManyTimesWithName(t, "SessionClosed", func(t testing.TB) {
   156  		ctx := xtest.Context(t)
   157  
   158  		sessionCtx, sessionCancel := xcontext.WithCancel(ctx)
   159  
   160  		session := newTestPartitionSession(sessionCtx, 1)
   161  		session.SetCommittedOffsetForward(1)
   162  		cRange := CommitRange{
   163  			CommitOffsetStart: 1,
   164  			CommitOffsetEnd:   2,
   165  			PartitionSession:  session,
   166  		}
   167  
   168  		c := newTestCommitter(ctx, t)
   169  		c.mode = CommitModeSync
   170  
   171  		waitErr := make(chan error)
   172  		go func() {
   173  			commitErr := c.Commit(ctx, cRange)
   174  			waitErr <- commitErr
   175  		}()
   176  
   177  		sessionCancel()
   178  
   179  		commitErr := <-waitErr
   180  		require.ErrorIs(t, commitErr, PublicErrCommitSessionToExpiredSession)
   181  	})
   182  }
   183  
   184  func TestCommitterBuffer(t *testing.T) {
   185  	t.Run("SendZeroLag", func(t *testing.T) {
   186  		ctx := xtest.Context(t)
   187  		c := newTestCommitter(ctx, t)
   188  
   189  		sendCalled := make(empty.Chan)
   190  		clock := clockwork.NewFakeClock()
   191  		c.clock = clock
   192  		c.send = func(msg rawtopicreader.ClientMessage) error {
   193  			close(sendCalled)
   194  
   195  			return nil
   196  		}
   197  
   198  		_, err := c.pushCommit(CommitRange{PartitionSession: newTestPartitionSession(
   199  			context.Background(), 2,
   200  		)})
   201  		require.NoError(t, err)
   202  		<-sendCalled
   203  	})
   204  	t.Run("TimeLagTrigger", func(t *testing.T) {
   205  		ctx := xtest.Context(t)
   206  		c := newTestCommitter(ctx, t)
   207  
   208  		sendCalled := make(empty.Chan)
   209  		isSended := func() bool {
   210  			select {
   211  			case <-sendCalled:
   212  				return true
   213  			default:
   214  				return false
   215  			}
   216  		}
   217  
   218  		clock := clockwork.NewFakeClock()
   219  		c.clock = clock
   220  		c.BufferTimeLagTrigger = time.Second
   221  		c.send = func(msg rawtopicreader.ClientMessage) error {
   222  			commitMess := msg.(*rawtopicreader.CommitOffsetRequest)
   223  			require.Len(t, commitMess.CommitOffsets, 2)
   224  			close(sendCalled)
   225  
   226  			return nil
   227  		}
   228  
   229  		_, err := c.pushCommit(CommitRange{PartitionSession: newTestPartitionSession(
   230  			context.Background(), 1,
   231  		)})
   232  		require.NoError(t, err)
   233  		_, err = c.pushCommit(CommitRange{PartitionSession: newTestPartitionSession(
   234  			context.Background(), 2,
   235  		)})
   236  		require.NoError(t, err)
   237  		require.False(t, isSended())
   238  
   239  		clock.BlockUntil(1)
   240  
   241  		clock.Advance(time.Second - 1)
   242  		time.Sleep(time.Millisecond)
   243  		require.False(t, isSended())
   244  
   245  		clock.Advance(1)
   246  		<-sendCalled
   247  	})
   248  	t.Run("CountAndTimeFireCountMoreThenNeed", func(t *testing.T) {
   249  		ctx := xtest.Context(t)
   250  		c := newTestCommitter(ctx, t)
   251  
   252  		sendCalled := make(empty.Chan)
   253  
   254  		clock := clockwork.NewFakeClock()
   255  		c.clock = clock
   256  		c.BufferTimeLagTrigger = time.Second // for prevent send
   257  		c.BufferCountTrigger = 2
   258  		c.send = func(msg rawtopicreader.ClientMessage) error {
   259  			commitMess := msg.(*rawtopicreader.CommitOffsetRequest)
   260  			require.Len(t, commitMess.CommitOffsets, 4)
   261  			close(sendCalled)
   262  
   263  			return nil
   264  		}
   265  		c.commits.AppendCommitRanges([]CommitRange{
   266  			{PartitionSession: newTestPartitionSession(
   267  				context.Background(), 1,
   268  			)},
   269  			{PartitionSession: newTestPartitionSession(
   270  				context.Background(), 2,
   271  			)},
   272  			{PartitionSession: newTestPartitionSession(
   273  				context.Background(), 3,
   274  			)},
   275  		})
   276  
   277  		_, err := c.pushCommit(CommitRange{PartitionSession: newTestPartitionSession(
   278  			context.Background(), 4,
   279  		)})
   280  		require.NoError(t, err)
   281  		<-sendCalled
   282  	})
   283  	t.Run("CountAndTimeFireCountOnAdd", func(t *testing.T) {
   284  		ctx := xtest.Context(t)
   285  		c := newTestCommitter(ctx, t)
   286  
   287  		sendCalled := make(empty.Chan)
   288  		isSended := func() bool {
   289  			select {
   290  			case <-sendCalled:
   291  				return true
   292  			default:
   293  				return false
   294  			}
   295  		}
   296  
   297  		clock := clockwork.NewFakeClock()
   298  		c.clock = clock
   299  		c.BufferTimeLagTrigger = time.Second // for prevent send
   300  		c.BufferCountTrigger = 4
   301  		c.send = func(msg rawtopicreader.ClientMessage) error {
   302  			commitMess := msg.(*rawtopicreader.CommitOffsetRequest)
   303  			require.Len(t, commitMess.CommitOffsets, 4)
   304  			close(sendCalled)
   305  
   306  			return nil
   307  		}
   308  
   309  		for i := 0; i < 3; i++ {
   310  			_, err := c.pushCommit(
   311  				CommitRange{
   312  					PartitionSession: newTestPartitionSession(
   313  						context.Background(), rawtopicreader.PartitionSessionID(i),
   314  					),
   315  				},
   316  			)
   317  			require.NoError(t, err)
   318  		}
   319  
   320  		// wait notify consumed
   321  		xtest.SpinWaitCondition(t, &c.m, func() bool {
   322  			return len(c.commits.Ranges) == 3
   323  		})
   324  		require.False(t, isSended())
   325  
   326  		_, err := c.pushCommit(CommitRange{PartitionSession: newTestPartitionSession(
   327  			context.Background(), 3,
   328  		)})
   329  		require.NoError(t, err)
   330  		<-sendCalled
   331  	})
   332  	t.Run("CountAndTimeFireTime", func(t *testing.T) {
   333  		ctx := xtest.Context(t)
   334  		clock := clockwork.NewFakeClock()
   335  		c := newTestCommitter(ctx, t)
   336  		c.clock = clock
   337  		c.BufferCountTrigger = 2
   338  		c.BufferTimeLagTrigger = time.Second
   339  
   340  		sendCalled := make(empty.Chan)
   341  		c.send = func(msg rawtopicreader.ClientMessage) error {
   342  			close(sendCalled)
   343  
   344  			return nil
   345  		}
   346  		_, err := c.pushCommit(CommitRange{PartitionSession: newTestPartitionSession(
   347  			context.Background(), 0,
   348  		)})
   349  		require.NoError(t, err)
   350  
   351  		clock.BlockUntil(1)
   352  		clock.Advance(time.Second)
   353  		<-sendCalled
   354  	})
   355  	t.Run("FireWithEmptyBuffer", func(t *testing.T) {
   356  		ctx := xtest.Context(t)
   357  		c := newTestCommitter(ctx, t)
   358  		c.send = func(msg rawtopicreader.ClientMessage) error {
   359  			t.Fatal()
   360  
   361  			return nil
   362  		}
   363  		c.commitLoopSignal <- empty.Struct{} // to buffer
   364  		c.commitLoopSignal <- empty.Struct{} // if send - first message consumed by send loop
   365  		c.commitLoopSignal <- empty.Struct{} // if send - second message consumed and first processed
   366  	})
   367  	t.Run("FlushOnClose", func(t *testing.T) {
   368  		ctx := xtest.Context(t)
   369  		c := newTestCommitter(ctx, t)
   370  
   371  		sendCalled := false
   372  		c.send = func(msg rawtopicreader.ClientMessage) error {
   373  			sendCalled = true
   374  
   375  			return nil
   376  		}
   377  		c.commits.AppendCommitRange(CommitRange{PartitionSession: newTestPartitionSession(
   378  			context.Background(), 0,
   379  		)})
   380  		require.NoError(t, c.Close(ctx, nil))
   381  		require.True(t, sendCalled)
   382  	})
   383  }
   384  
   385  func newTestCommitter(ctx context.Context, t testing.TB) *Committer {
   386  	res := NewCommitterStopped(&trace.Topic{}, ctx, CommitModeAsync, func(msg rawtopicreader.ClientMessage) error {
   387  		return nil
   388  	})
   389  	res.Start()
   390  	t.Cleanup(func() {
   391  		if err := res.Close(ctx, errors.New("test committer closed")); err != nil {
   392  			require.ErrorIs(t, err, background.ErrAlreadyClosed)
   393  		}
   394  	})
   395  
   396  	return res
   397  }
   398  
   399  func newTestPartitionSession(
   400  	ctx context.Context,
   401  	partitionSessionID rawtopicreader.PartitionSessionID,
   402  ) *PartitionSession {
   403  	return NewPartitionSession(
   404  		ctx,
   405  		"",
   406  		0,
   407  		-1,
   408  		"",
   409  		partitionSessionID,
   410  		int64(partitionSessionID)+100,
   411  		0,
   412  	)
   413  }
   414  
   415  func testNewCommitRanges(commitable ...PublicCommitRangeGetter) *CommitRanges {
   416  	var res CommitRanges
   417  	res.Append(commitable...)
   418  
   419  	return &res
   420  }