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

     1  package topicreaderinternal
     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/rawtopicreader"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    18  )
    19  
    20  func TestCommitterCommit(t *testing.T) {
    21  	t.Run("CommitWithCancelledContext", func(t *testing.T) {
    22  		ctx := xtest.Context(t)
    23  		c := newTestCommitter(ctx, t)
    24  		c.send = func(msg rawtopicreader.ClientMessage) error {
    25  			t.Fatalf("must not call")
    26  
    27  			return nil
    28  		}
    29  
    30  		ctx, cancel := xcontext.WithCancel(ctx)
    31  		cancel()
    32  
    33  		err := c.Commit(ctx, commitRange{})
    34  		require.ErrorIs(t, err, context.Canceled)
    35  	})
    36  }
    37  
    38  func TestCommitterCommitDisabled(t *testing.T) {
    39  	ctx := xtest.Context(t)
    40  	c := &committer{mode: CommitModeNone}
    41  	err := c.Commit(ctx, commitRange{})
    42  	require.ErrorIs(t, err, ErrCommitDisabled)
    43  }
    44  
    45  func TestCommitterCommitAsync(t *testing.T) {
    46  	t.Run("SendCommit", func(t *testing.T) {
    47  		ctx := xtest.Context(t)
    48  		session := &partitionSession{
    49  			ctx:                context.Background(),
    50  			partitionSessionID: 1,
    51  		}
    52  
    53  		cRange := commitRange{
    54  			commitOffsetStart: 1,
    55  			commitOffsetEnd:   2,
    56  			partitionSession:  session,
    57  		}
    58  
    59  		sendCalled := make(empty.Chan)
    60  		c := newTestCommitter(ctx, t)
    61  		c.mode = CommitModeAsync
    62  		c.send = func(msg rawtopicreader.ClientMessage) error {
    63  			close(sendCalled)
    64  			require.Equal(t,
    65  				&rawtopicreader.CommitOffsetRequest{
    66  					CommitOffsets: testNewCommitRanges(&cRange).toPartitionsOffsets(),
    67  				},
    68  				msg)
    69  
    70  			return nil
    71  		}
    72  		require.NoError(t, c.Commit(ctx, cRange))
    73  		<-sendCalled
    74  	})
    75  }
    76  
    77  func TestCommitterCommitSync(t *testing.T) {
    78  	t.Run("SendCommit", func(t *testing.T) {
    79  		ctx := xtest.Context(t)
    80  		session := &partitionSession{
    81  			ctx:                context.Background(),
    82  			partitionSessionID: 1,
    83  		}
    84  
    85  		cRange := commitRange{
    86  			commitOffsetStart: 1,
    87  			commitOffsetEnd:   2,
    88  			partitionSession:  session,
    89  		}
    90  
    91  		sendCalled := false
    92  		c := newTestCommitter(ctx, t)
    93  		c.mode = CommitModeSync
    94  		c.send = func(msg rawtopicreader.ClientMessage) error {
    95  			sendCalled = true
    96  			require.Equal(t,
    97  				&rawtopicreader.CommitOffsetRequest{
    98  					CommitOffsets: testNewCommitRanges(&cRange).toPartitionsOffsets(),
    99  				},
   100  				msg)
   101  			c.OnCommitNotify(session, cRange.commitOffsetEnd)
   102  
   103  			return nil
   104  		}
   105  		require.NoError(t, c.Commit(ctx, cRange))
   106  		require.True(t, sendCalled)
   107  	})
   108  
   109  	xtest.TestManyTimesWithName(t, "SuccessCommitWithNotifyAfterCommit", func(t testing.TB) {
   110  		ctx := xtest.Context(t)
   111  		session := &partitionSession{
   112  			ctx:                context.Background(),
   113  			partitionSessionID: 1,
   114  		}
   115  
   116  		cRange := commitRange{
   117  			commitOffsetStart: 1,
   118  			commitOffsetEnd:   2,
   119  			partitionSession:  session,
   120  		}
   121  
   122  		commitSended := make(empty.Chan)
   123  		c := newTestCommitter(ctx, t)
   124  		c.mode = CommitModeSync
   125  		c.send = func(msg rawtopicreader.ClientMessage) error {
   126  			close(commitSended)
   127  
   128  			return nil
   129  		}
   130  
   131  		commitCompleted := make(empty.Chan)
   132  		go func() {
   133  			require.NoError(t, c.Commit(ctx, cRange))
   134  			close(commitCompleted)
   135  		}()
   136  
   137  		notifySended := false
   138  		go func() {
   139  			<-commitSended
   140  			notifySended = true
   141  			c.OnCommitNotify(session, rawtopicreader.Offset(2))
   142  		}()
   143  
   144  		<-commitCompleted
   145  		require.True(t, notifySended)
   146  	})
   147  
   148  	t.Run("SuccessCommitPreviousCommitted", func(t *testing.T) {
   149  		ctx := xtest.Context(t)
   150  		session := &partitionSession{
   151  			ctx:                ctx,
   152  			partitionSessionID: 1,
   153  		}
   154  		session.committedOffsetVal.Store(2)
   155  
   156  		cRange := commitRange{
   157  			commitOffsetStart: 1,
   158  			commitOffsetEnd:   2,
   159  			partitionSession:  session,
   160  		}
   161  
   162  		c := newTestCommitter(ctx, t)
   163  		require.NoError(t, c.Commit(ctx, cRange))
   164  	})
   165  
   166  	xtest.TestManyTimesWithName(t, "SessionClosed", func(t testing.TB) {
   167  		ctx := xtest.Context(t)
   168  
   169  		sessionCtx, sessionCancel := xcontext.WithCancel(ctx)
   170  
   171  		session := &partitionSession{
   172  			ctx:                sessionCtx,
   173  			partitionSessionID: 1,
   174  		}
   175  		session.committedOffsetVal.Store(1)
   176  		cRange := commitRange{
   177  			commitOffsetStart: 1,
   178  			commitOffsetEnd:   2,
   179  			partitionSession:  session,
   180  		}
   181  
   182  		c := newTestCommitter(ctx, t)
   183  		c.mode = CommitModeSync
   184  
   185  		waitErr := make(chan error)
   186  		go func() {
   187  			commitErr := c.Commit(ctx, cRange)
   188  			waitErr <- commitErr
   189  		}()
   190  
   191  		sessionCancel()
   192  
   193  		commitErr := <-waitErr
   194  		require.ErrorIs(t, commitErr, PublicErrCommitSessionToExpiredSession)
   195  	})
   196  }
   197  
   198  func TestCommitterBuffer(t *testing.T) {
   199  	t.Run("SendZeroLag", func(t *testing.T) {
   200  		ctx := xtest.Context(t)
   201  		c := newTestCommitter(ctx, t)
   202  
   203  		sendCalled := make(empty.Chan)
   204  		clock := clockwork.NewFakeClock()
   205  		c.clock = clock
   206  		c.send = func(msg rawtopicreader.ClientMessage) error {
   207  			close(sendCalled)
   208  
   209  			return nil
   210  		}
   211  
   212  		_, err := c.pushCommit(commitRange{partitionSession: &partitionSession{partitionSessionID: 2}})
   213  		require.NoError(t, err)
   214  		<-sendCalled
   215  	})
   216  	t.Run("TimeLagTrigger", func(t *testing.T) {
   217  		ctx := xtest.Context(t)
   218  		c := newTestCommitter(ctx, t)
   219  
   220  		sendCalled := make(empty.Chan)
   221  		isSended := func() bool {
   222  			select {
   223  			case <-sendCalled:
   224  				return true
   225  			default:
   226  				return false
   227  			}
   228  		}
   229  
   230  		clock := clockwork.NewFakeClock()
   231  		c.clock = clock
   232  		c.BufferTimeLagTrigger = time.Second
   233  		c.send = func(msg rawtopicreader.ClientMessage) error {
   234  			commitMess := msg.(*rawtopicreader.CommitOffsetRequest)
   235  			require.Len(t, commitMess.CommitOffsets, 2)
   236  			close(sendCalled)
   237  
   238  			return nil
   239  		}
   240  
   241  		_, err := c.pushCommit(commitRange{partitionSession: &partitionSession{partitionSessionID: 1}})
   242  		require.NoError(t, err)
   243  		_, err = c.pushCommit(commitRange{partitionSession: &partitionSession{partitionSessionID: 2}})
   244  		require.NoError(t, err)
   245  		require.False(t, isSended())
   246  
   247  		clock.BlockUntil(1)
   248  
   249  		clock.Advance(time.Second - 1)
   250  		time.Sleep(time.Millisecond)
   251  		require.False(t, isSended())
   252  
   253  		clock.Advance(1)
   254  		<-sendCalled
   255  	})
   256  	t.Run("CountAndTimeFireCountMoreThenNeed", func(t *testing.T) {
   257  		ctx := xtest.Context(t)
   258  		c := newTestCommitter(ctx, t)
   259  
   260  		sendCalled := make(empty.Chan)
   261  
   262  		clock := clockwork.NewFakeClock()
   263  		c.clock = clock
   264  		c.BufferTimeLagTrigger = time.Second // for prevent send
   265  		c.BufferCountTrigger = 2
   266  		c.send = func(msg rawtopicreader.ClientMessage) error {
   267  			commitMess := msg.(*rawtopicreader.CommitOffsetRequest)
   268  			require.Len(t, commitMess.CommitOffsets, 4)
   269  			close(sendCalled)
   270  
   271  			return nil
   272  		}
   273  		c.commits.appendCommitRanges([]commitRange{
   274  			{partitionSession: &partitionSession{partitionSessionID: 1}},
   275  			{partitionSession: &partitionSession{partitionSessionID: 2}},
   276  			{partitionSession: &partitionSession{partitionSessionID: 3}},
   277  		})
   278  
   279  		_, err := c.pushCommit(commitRange{partitionSession: &partitionSession{partitionSessionID: 4}})
   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: &partitionSession{
   313  						partitionSessionID: 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: &partitionSession{partitionSessionID: 3}})
   327  		require.NoError(t, err)
   328  		<-sendCalled
   329  	})
   330  	t.Run("CountAndTimeFireTime", func(t *testing.T) {
   331  		ctx := xtest.Context(t)
   332  		clock := clockwork.NewFakeClock()
   333  		c := newTestCommitter(ctx, t)
   334  		c.clock = clock
   335  		c.BufferCountTrigger = 2
   336  		c.BufferTimeLagTrigger = time.Second
   337  
   338  		sendCalled := make(empty.Chan)
   339  		c.send = func(msg rawtopicreader.ClientMessage) error {
   340  			close(sendCalled)
   341  
   342  			return nil
   343  		}
   344  		_, err := c.pushCommit(commitRange{partitionSession: &partitionSession{}})
   345  		require.NoError(t, err)
   346  
   347  		clock.BlockUntil(1)
   348  		clock.Advance(time.Second)
   349  		<-sendCalled
   350  	})
   351  	t.Run("FireWithEmptyBuffer", func(t *testing.T) {
   352  		ctx := xtest.Context(t)
   353  		c := newTestCommitter(ctx, t)
   354  		c.send = func(msg rawtopicreader.ClientMessage) error {
   355  			t.Fatal()
   356  
   357  			return nil
   358  		}
   359  		c.commitLoopSignal <- empty.Struct{} // to buffer
   360  		c.commitLoopSignal <- empty.Struct{} // if send - first message consumed by send loop
   361  		c.commitLoopSignal <- empty.Struct{} // if send - second message consumed and first processed
   362  	})
   363  	t.Run("FlushOnClose", func(t *testing.T) {
   364  		ctx := xtest.Context(t)
   365  		c := newTestCommitter(ctx, t)
   366  
   367  		sendCalled := false
   368  		c.send = func(msg rawtopicreader.ClientMessage) error {
   369  			sendCalled = true
   370  
   371  			return nil
   372  		}
   373  		c.commits.appendCommitRange(commitRange{partitionSession: &partitionSession{}})
   374  		require.NoError(t, c.Close(ctx, nil))
   375  		require.True(t, sendCalled)
   376  	})
   377  }
   378  
   379  func newTestCommitter(ctx context.Context, t testing.TB) *committer {
   380  	res := newCommitter(&trace.Topic{}, ctx, CommitModeAsync, func(msg rawtopicreader.ClientMessage) error {
   381  		return nil
   382  	})
   383  	t.Cleanup(func() {
   384  		if err := res.Close(ctx, errors.New("test comitter closed")); err != nil {
   385  			require.ErrorIs(t, err, background.ErrAlreadyClosed)
   386  		}
   387  	})
   388  
   389  	return res
   390  }