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 }