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  }