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 }