github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/sink/manager_test.go (about) 1 // Copyright 2021 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package sink 15 16 import ( 17 "context" 18 "math" 19 "math/rand" 20 "sync" 21 "sync/atomic" 22 "time" 23 24 "github.com/pingcap/errors" 25 26 "github.com/pingcap/check" 27 "github.com/pingcap/ticdc/cdc/model" 28 "github.com/pingcap/ticdc/pkg/util/testleak" 29 ) 30 31 type managerSuite struct{} 32 33 var _ = check.Suite(&managerSuite{}) 34 35 type checkSink struct { 36 *check.C 37 rows []*model.RowChangedEvent 38 rowsMu sync.Mutex 39 lastResolvedTs uint64 40 } 41 42 func (c *checkSink) Initialize(ctx context.Context, tableInfo []*model.SimpleTableInfo) error { 43 panic("unreachable") 44 } 45 46 func (c *checkSink) EmitRowChangedEvents(ctx context.Context, rows ...*model.RowChangedEvent) error { 47 c.rowsMu.Lock() 48 defer c.rowsMu.Unlock() 49 c.rows = append(c.rows, rows...) 50 return nil 51 } 52 53 func (c *checkSink) EmitDDLEvent(ctx context.Context, ddl *model.DDLEvent) error { 54 panic("unreachable") 55 } 56 57 func (c *checkSink) FlushRowChangedEvents(ctx context.Context, resolvedTs uint64) (uint64, error) { 58 c.rowsMu.Lock() 59 defer c.rowsMu.Unlock() 60 var newRows []*model.RowChangedEvent 61 for _, row := range c.rows { 62 if row.CommitTs <= c.lastResolvedTs { 63 return c.lastResolvedTs, errors.Errorf("commit-ts(%d) is not greater than lastResolvedTs(%d)", row.CommitTs, c.lastResolvedTs) 64 } 65 if row.CommitTs > resolvedTs { 66 newRows = append(newRows, row) 67 } 68 } 69 70 c.Assert(c.lastResolvedTs, check.LessEqual, resolvedTs) 71 c.lastResolvedTs = resolvedTs 72 c.rows = newRows 73 74 return c.lastResolvedTs, nil 75 } 76 77 func (c *checkSink) EmitCheckpointTs(ctx context.Context, ts uint64) error { 78 panic("unreachable") 79 } 80 81 func (c *checkSink) Close(ctx context.Context) error { 82 return nil 83 } 84 85 func (c *checkSink) Barrier(ctx context.Context) error { 86 return nil 87 } 88 89 func (s *managerSuite) TestManagerRandom(c *check.C) { 90 defer testleak.AfterTest(c)() 91 ctx, cancel := context.WithCancel(context.Background()) 92 defer cancel() 93 errCh := make(chan error, 16) 94 manager := NewManager(ctx, &checkSink{C: c}, errCh, 0) 95 defer manager.Close(ctx) 96 goroutineNum := 10 97 rowNum := 100 98 var wg sync.WaitGroup 99 tableSinks := make([]Sink, goroutineNum) 100 for i := 0; i < goroutineNum; i++ { 101 i := i 102 wg.Add(1) 103 go func() { 104 defer wg.Done() 105 tableSinks[i] = manager.CreateTableSink(model.TableID(i), 0) 106 }() 107 } 108 wg.Wait() 109 for i := 0; i < goroutineNum; i++ { 110 i := i 111 tableSink := tableSinks[i] 112 wg.Add(1) 113 go func() { 114 defer wg.Done() 115 ctx := context.Background() 116 var lastResolvedTs uint64 117 for j := 1; j < rowNum; j++ { 118 if rand.Intn(10) == 0 { 119 resolvedTs := lastResolvedTs + uint64(rand.Intn(j-int(lastResolvedTs))) 120 _, err := tableSink.FlushRowChangedEvents(ctx, resolvedTs) 121 c.Assert(err, check.IsNil) 122 lastResolvedTs = resolvedTs 123 } else { 124 err := tableSink.EmitRowChangedEvents(ctx, &model.RowChangedEvent{ 125 Table: &model.TableName{TableID: int64(i)}, 126 CommitTs: uint64(j), 127 }) 128 c.Assert(err, check.IsNil) 129 } 130 } 131 _, err := tableSink.FlushRowChangedEvents(ctx, uint64(rowNum)) 132 c.Assert(err, check.IsNil) 133 }() 134 } 135 wg.Wait() 136 cancel() 137 time.Sleep(1 * time.Second) 138 close(errCh) 139 for err := range errCh { 140 c.Assert(err, check.IsNil) 141 } 142 } 143 144 func (s *managerSuite) TestManagerAddRemoveTable(c *check.C) { 145 defer testleak.AfterTest(c)() 146 ctx, cancel := context.WithCancel(context.Background()) 147 defer cancel() 148 errCh := make(chan error, 16) 149 manager := NewManager(ctx, &checkSink{C: c}, errCh, 0) 150 defer manager.Close(ctx) 151 goroutineNum := 200 152 var wg sync.WaitGroup 153 const ExitSignal = uint64(math.MaxUint64) 154 155 var maxResolvedTs uint64 156 tableSinks := make([]Sink, 0, goroutineNum) 157 tableCancels := make([]context.CancelFunc, 0, goroutineNum) 158 runTableSink := func(ctx context.Context, index int64, sink Sink, startTs uint64) { 159 defer wg.Done() 160 lastResolvedTs := startTs 161 for { 162 select { 163 case <-ctx.Done(): 164 return 165 default: 166 } 167 resolvedTs := atomic.LoadUint64(&maxResolvedTs) 168 if resolvedTs == ExitSignal { 169 return 170 } 171 if resolvedTs == lastResolvedTs { 172 time.Sleep(10 * time.Millisecond) 173 continue 174 } 175 for i := lastResolvedTs + 1; i <= resolvedTs; i++ { 176 err := sink.EmitRowChangedEvents(ctx, &model.RowChangedEvent{ 177 Table: &model.TableName{TableID: index}, 178 CommitTs: i, 179 }) 180 c.Assert(err, check.IsNil) 181 } 182 _, err := sink.FlushRowChangedEvents(ctx, resolvedTs) 183 if err != nil { 184 c.Assert(errors.Cause(err), check.Equals, context.Canceled) 185 } 186 lastResolvedTs = resolvedTs 187 } 188 } 189 190 wg.Add(1) 191 go func() { 192 defer wg.Done() 193 // add three table and then remote one table 194 for i := 0; i < goroutineNum; i++ { 195 if i%4 != 3 { 196 // add table 197 table := manager.CreateTableSink(model.TableID(i), maxResolvedTs) 198 ctx, cancel := context.WithCancel(ctx) 199 tableCancels = append(tableCancels, cancel) 200 tableSinks = append(tableSinks, table) 201 atomic.AddUint64(&maxResolvedTs, 20) 202 wg.Add(1) 203 go runTableSink(ctx, int64(i), table, maxResolvedTs) 204 } else { 205 // remove table 206 table := tableSinks[0] 207 // note when a table is removed, no more data can be sent to the 208 // backend sink, so we cancel the context of this table sink. 209 tableCancels[0]() 210 c.Assert(table.Close(ctx), check.IsNil) 211 tableSinks = tableSinks[1:] 212 tableCancels = tableCancels[1:] 213 } 214 time.Sleep(10 * time.Millisecond) 215 } 216 atomic.StoreUint64(&maxResolvedTs, ExitSignal) 217 }() 218 219 wg.Wait() 220 cancel() 221 time.Sleep(1 * time.Second) 222 close(errCh) 223 for err := range errCh { 224 c.Assert(err, check.IsNil) 225 } 226 } 227 228 func (s *managerSuite) TestManagerDestroyTableSink(c *check.C) { 229 defer testleak.AfterTest(c)() 230 ctx, cancel := context.WithCancel(context.Background()) 231 defer cancel() 232 233 errCh := make(chan error, 16) 234 manager := NewManager(ctx, &checkSink{C: c}, errCh, 0) 235 defer manager.Close(ctx) 236 237 tableID := int64(49) 238 tableSink := manager.CreateTableSink(tableID, 100) 239 err := tableSink.EmitRowChangedEvents(ctx, &model.RowChangedEvent{ 240 Table: &model.TableName{TableID: tableID}, 241 CommitTs: uint64(110), 242 }) 243 c.Assert(err, check.IsNil) 244 _, err = tableSink.FlushRowChangedEvents(ctx, 110) 245 c.Assert(err, check.IsNil) 246 err = manager.destroyTableSink(ctx, tableID) 247 c.Assert(err, check.IsNil) 248 } 249 250 type errorSink struct { 251 *check.C 252 } 253 254 func (e *errorSink) Initialize(ctx context.Context, tableInfo []*model.SimpleTableInfo) error { 255 panic("unreachable") 256 } 257 258 func (e *errorSink) EmitRowChangedEvents(ctx context.Context, rows ...*model.RowChangedEvent) error { 259 return errors.New("error in emit row changed events") 260 } 261 262 func (e *errorSink) EmitDDLEvent(ctx context.Context, ddl *model.DDLEvent) error { 263 panic("unreachable") 264 } 265 266 func (e *errorSink) FlushRowChangedEvents(ctx context.Context, resolvedTs uint64) (uint64, error) { 267 return 0, errors.New("error in flush row changed events") 268 } 269 270 func (e *errorSink) EmitCheckpointTs(ctx context.Context, ts uint64) error { 271 panic("unreachable") 272 } 273 274 func (e *errorSink) Close(ctx context.Context) error { 275 return nil 276 } 277 278 func (e *errorSink) Barrier(ctx context.Context) error { 279 return nil 280 } 281 282 func (s *managerSuite) TestManagerError(c *check.C) { 283 defer testleak.AfterTest(c)() 284 ctx, cancel := context.WithCancel(context.Background()) 285 defer cancel() 286 errCh := make(chan error, 16) 287 manager := NewManager(ctx, &errorSink{C: c}, errCh, 0) 288 defer manager.Close(ctx) 289 sink := manager.CreateTableSink(1, 0) 290 err := sink.EmitRowChangedEvents(ctx, &model.RowChangedEvent{ 291 CommitTs: 1, 292 Table: &model.TableName{TableID: 1}, 293 }) 294 c.Assert(err, check.IsNil) 295 _, err = sink.FlushRowChangedEvents(ctx, 2) 296 c.Assert(err, check.IsNil) 297 err = <-errCh 298 c.Assert(err.Error(), check.Equals, "error in emit row changed events") 299 }