github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/puller/sorter/backend_pool_test.go (about)

     1  // Copyright 2020 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 sorter
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"strconv"
    22  	"time"
    23  
    24  	"github.com/pingcap/check"
    25  	"github.com/pingcap/failpoint"
    26  	"github.com/pingcap/ticdc/pkg/config"
    27  	"github.com/pingcap/ticdc/pkg/filelock"
    28  	"github.com/pingcap/ticdc/pkg/util/testleak"
    29  )
    30  
    31  type backendPoolSuite struct{}
    32  
    33  var _ = check.SerialSuites(&backendPoolSuite{})
    34  
    35  func (s *backendPoolSuite) TestBasicFunction(c *check.C) {
    36  	defer testleak.AfterTest(c)()
    37  
    38  	dataDir := c.MkDir()
    39  	err := os.MkdirAll(dataDir, 0o755)
    40  	c.Assert(err, check.IsNil)
    41  
    42  	sortDir := filepath.Join(dataDir, config.DefaultSortDir)
    43  	err = os.MkdirAll(sortDir, 0o755)
    44  	c.Assert(err, check.IsNil)
    45  
    46  	conf := config.GetDefaultServerConfig()
    47  	conf.DataDir = dataDir
    48  	conf.Sorter.SortDir = sortDir
    49  	conf.Sorter.MaxMemoryPressure = 90                         // 90%
    50  	conf.Sorter.MaxMemoryConsumption = 16 * 1024 * 1024 * 1024 // 16G
    51  	config.StoreGlobalServerConfig(conf)
    52  
    53  	err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint", "return(100)")
    54  	c.Assert(err, check.IsNil)
    55  
    56  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
    57  	defer cancel()
    58  
    59  	backEndPool, err := newBackEndPool(sortDir, "")
    60  	c.Assert(err, check.IsNil)
    61  	c.Assert(backEndPool, check.NotNil)
    62  	defer backEndPool.terminate()
    63  
    64  	backEnd, err := backEndPool.alloc(ctx)
    65  	c.Assert(err, check.IsNil)
    66  	c.Assert(backEnd, check.FitsTypeOf, &fileBackEnd{})
    67  	fileName := backEnd.(*fileBackEnd).fileName
    68  	c.Assert(fileName, check.Not(check.Equals), "")
    69  
    70  	err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint", "return(0)")
    71  	c.Assert(err, check.IsNil)
    72  	err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryUsageInjectPoint", "return(34359738368)")
    73  	c.Assert(err, check.IsNil)
    74  
    75  	backEnd1, err := backEndPool.alloc(ctx)
    76  	c.Assert(err, check.IsNil)
    77  	c.Assert(backEnd1, check.FitsTypeOf, &fileBackEnd{})
    78  	fileName1 := backEnd1.(*fileBackEnd).fileName
    79  	c.Assert(fileName1, check.Not(check.Equals), "")
    80  	c.Assert(fileName1, check.Not(check.Equals), fileName)
    81  
    82  	err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint", "return(0)")
    83  	c.Assert(err, check.IsNil)
    84  	err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryUsageInjectPoint", "return(0)")
    85  	c.Assert(err, check.IsNil)
    86  
    87  	backEnd2, err := backEndPool.alloc(ctx)
    88  	c.Assert(err, check.IsNil)
    89  	c.Assert(backEnd2, check.FitsTypeOf, &memoryBackEnd{})
    90  
    91  	err = backEndPool.dealloc(backEnd)
    92  	c.Assert(err, check.IsNil)
    93  
    94  	err = backEndPool.dealloc(backEnd1)
    95  	c.Assert(err, check.IsNil)
    96  
    97  	err = backEndPool.dealloc(backEnd2)
    98  	c.Assert(err, check.IsNil)
    99  
   100  	time.Sleep(backgroundJobInterval * 3 / 2)
   101  
   102  	_, err = os.Stat(fileName)
   103  	c.Assert(os.IsNotExist(err), check.IsTrue)
   104  
   105  	_, err = os.Stat(fileName1)
   106  	c.Assert(os.IsNotExist(err), check.IsTrue)
   107  }
   108  
   109  // TestDirectoryBadPermission verifies that no permission to ls the directory does not prevent using it
   110  // as a temporary file directory.
   111  func (s *backendPoolSuite) TestDirectoryBadPermission(c *check.C) {
   112  	defer testleak.AfterTest(c)()
   113  
   114  	dataDir := c.MkDir()
   115  	sortDir := filepath.Join(dataDir, config.DefaultSortDir)
   116  	err := os.MkdirAll(sortDir, 0o755)
   117  	c.Assert(err, check.IsNil)
   118  
   119  	err = os.Chmod(sortDir, 0o311) // no permission to `ls`
   120  	c.Assert(err, check.IsNil)
   121  
   122  	conf := config.GetGlobalServerConfig()
   123  	conf.DataDir = dataDir
   124  	conf.Sorter.SortDir = sortDir
   125  	conf.Sorter.MaxMemoryPressure = 0 // force using files
   126  
   127  	backEndPool, err := newBackEndPool(sortDir, "")
   128  	c.Assert(err, check.IsNil)
   129  	c.Assert(backEndPool, check.NotNil)
   130  	defer backEndPool.terminate()
   131  
   132  	backEnd, err := backEndPool.alloc(context.Background())
   133  	c.Assert(err, check.IsNil)
   134  	defer backEnd.free() //nolint:errcheck
   135  
   136  	fileName := backEnd.(*fileBackEnd).fileName
   137  	_, err = os.Stat(fileName)
   138  	c.Assert(err, check.IsNil) // assert that the file exists
   139  
   140  	err = backEndPool.dealloc(backEnd)
   141  	c.Assert(err, check.IsNil)
   142  }
   143  
   144  // TestCleanUpSelf verifies that the backendPool correctly cleans up files used by itself on exit.
   145  func (s *backendPoolSuite) TestCleanUpSelf(c *check.C) {
   146  	defer testleak.AfterTest(c)()
   147  
   148  	dataDir := c.MkDir()
   149  	err := os.Chmod(dataDir, 0o755)
   150  	c.Assert(err, check.IsNil)
   151  
   152  	sorterDir := filepath.Join(dataDir, config.DefaultSortDir)
   153  	err = os.MkdirAll(sorterDir, 0o755)
   154  	c.Assert(err, check.IsNil)
   155  
   156  	conf := config.GetDefaultServerConfig()
   157  	conf.DataDir = dataDir
   158  	conf.Sorter.SortDir = sorterDir
   159  	conf.Sorter.MaxMemoryPressure = 90                         // 90%
   160  	conf.Sorter.MaxMemoryConsumption = 16 * 1024 * 1024 * 1024 // 16G
   161  	config.StoreGlobalServerConfig(conf)
   162  
   163  	err = failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint", "return(100)")
   164  	c.Assert(err, check.IsNil)
   165  	defer failpoint.Disable("github.com/pingcap/ticdc/cdc/puller/sorter/memoryPressureInjectPoint") //nolint:errcheck
   166  
   167  	backEndPool, err := newBackEndPool(sorterDir, "")
   168  	c.Assert(err, check.IsNil)
   169  	c.Assert(backEndPool, check.NotNil)
   170  
   171  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
   172  	defer cancel()
   173  
   174  	var fileNames []string
   175  	for i := 0; i < 20; i++ {
   176  		backEnd, err := backEndPool.alloc(ctx)
   177  		c.Assert(err, check.IsNil)
   178  		c.Assert(backEnd, check.FitsTypeOf, &fileBackEnd{})
   179  
   180  		fileName := backEnd.(*fileBackEnd).fileName
   181  		_, err = os.Stat(fileName)
   182  		c.Assert(err, check.IsNil)
   183  
   184  		fileNames = append(fileNames, fileName)
   185  	}
   186  
   187  	prefix := backEndPool.filePrefix
   188  	c.Assert(prefix, check.Not(check.Equals), "")
   189  
   190  	for j := 100; j < 120; j++ {
   191  		fileName := prefix + strconv.Itoa(j) + ".tmp"
   192  		f, err := os.Create(fileName)
   193  		c.Assert(err, check.IsNil)
   194  		err = f.Close()
   195  		c.Assert(err, check.IsNil)
   196  
   197  		fileNames = append(fileNames, fileName)
   198  	}
   199  
   200  	backEndPool.terminate()
   201  
   202  	for _, fileName := range fileNames {
   203  		_, err = os.Stat(fileName)
   204  		c.Assert(os.IsNotExist(err), check.IsTrue)
   205  	}
   206  }
   207  
   208  type mockOtherProcess struct {
   209  	dir    string
   210  	prefix string
   211  	flock  *filelock.FileLock
   212  	files  []string
   213  }
   214  
   215  func newMockOtherProcess(c *check.C, dir string, prefix string) *mockOtherProcess {
   216  	prefixLockPath := fmt.Sprintf("%s/%s", dir, sortDirLockFileName)
   217  	flock, err := filelock.NewFileLock(prefixLockPath)
   218  	c.Assert(err, check.IsNil)
   219  
   220  	err = flock.Lock()
   221  	c.Assert(err, check.IsNil)
   222  
   223  	return &mockOtherProcess{
   224  		dir:    dir,
   225  		prefix: prefix,
   226  		flock:  flock,
   227  	}
   228  }
   229  
   230  func (p *mockOtherProcess) writeMockFiles(c *check.C, num int) {
   231  	for i := 0; i < num; i++ {
   232  		fileName := fmt.Sprintf("%s%d", p.prefix, i)
   233  		f, err := os.Create(fileName)
   234  		c.Assert(err, check.IsNil)
   235  		_ = f.Close()
   236  		p.files = append(p.files, fileName)
   237  	}
   238  }
   239  
   240  func (p *mockOtherProcess) changeLockPermission(c *check.C, mode os.FileMode) {
   241  	prefixLockPath := fmt.Sprintf("%s/%s", p.dir, sortDirLockFileName)
   242  	err := os.Chmod(prefixLockPath, mode)
   243  	c.Assert(err, check.IsNil)
   244  }
   245  
   246  func (p *mockOtherProcess) unlock(c *check.C) {
   247  	err := p.flock.Unlock()
   248  	c.Assert(err, check.IsNil)
   249  }
   250  
   251  func (p *mockOtherProcess) assertFilesExist(c *check.C) {
   252  	for _, file := range p.files {
   253  		_, err := os.Stat(file)
   254  		c.Assert(err, check.IsNil)
   255  	}
   256  }
   257  
   258  func (p *mockOtherProcess) assertFilesNotExist(c *check.C) {
   259  	for _, file := range p.files {
   260  		_, err := os.Stat(file)
   261  		c.Assert(os.IsNotExist(err), check.IsTrue)
   262  	}
   263  }
   264  
   265  // TestCleanUpStaleBasic verifies that the backendPool correctly cleans up stale temporary files
   266  // left by other CDC processes that have exited abnormally.
   267  func (s *backendPoolSuite) TestCleanUpStaleBasic(c *check.C) {
   268  	defer testleak.AfterTest(c)()
   269  
   270  	dir := c.MkDir()
   271  	prefix := dir + "/sort-1-"
   272  
   273  	mockP := newMockOtherProcess(c, dir, prefix)
   274  	mockP.writeMockFiles(c, 100)
   275  	mockP.unlock(c)
   276  	mockP.assertFilesExist(c)
   277  
   278  	backEndPool, err := newBackEndPool(dir, "")
   279  	c.Assert(err, check.IsNil)
   280  	c.Assert(backEndPool, check.NotNil)
   281  	defer backEndPool.terminate()
   282  
   283  	mockP.assertFilesNotExist(c)
   284  }
   285  
   286  // TestFileLockConflict tests that if two backEndPools were to use the same sort-dir,
   287  // and error would be returned by one of them.
   288  func (s *backendPoolSuite) TestFileLockConflict(c *check.C) {
   289  	defer testleak.AfterTest(c)()
   290  	dir := c.MkDir()
   291  
   292  	backEndPool1, err := newBackEndPool(dir, "")
   293  	c.Assert(err, check.IsNil)
   294  	c.Assert(backEndPool1, check.NotNil)
   295  	defer backEndPool1.terminate()
   296  
   297  	backEndPool2, err := newBackEndPool(dir, "")
   298  	c.Assert(err, check.ErrorMatches, ".*file lock conflict.*")
   299  	c.Assert(backEndPool2, check.IsNil)
   300  }
   301  
   302  // TestCleanUpStaleBasic verifies that the backendPool correctly cleans up stale temporary files
   303  // left by other CDC processes that have exited abnormally.
   304  func (s *backendPoolSuite) TestCleanUpStaleLockNoPermission(c *check.C) {
   305  	defer testleak.AfterTest(c)()
   306  
   307  	dir := c.MkDir()
   308  	prefix := dir + "/sort-1-"
   309  
   310  	mockP := newMockOtherProcess(c, dir, prefix)
   311  	mockP.writeMockFiles(c, 100)
   312  	// set a bad permission
   313  	mockP.changeLockPermission(c, 0o000)
   314  
   315  	backEndPool, err := newBackEndPool(dir, "")
   316  	c.Assert(err, check.ErrorMatches, ".*permission denied.*")
   317  	c.Assert(backEndPool, check.IsNil)
   318  
   319  	mockP.assertFilesExist(c)
   320  }
   321  
   322  // TestGetMemoryPressureFailure verifies that the backendPool can handle gracefully failures that happen when
   323  // getting the current system memory pressure. Such a failure is usually caused by a lack of file descriptor quota
   324  // set by the operating system.
   325  func (s *backendPoolSuite) TestGetMemoryPressureFailure(c *check.C) {
   326  	defer testleak.AfterTest(c)()
   327  
   328  	err := failpoint.Enable("github.com/pingcap/ticdc/cdc/puller/sorter/getMemoryPressureFails", "return(true)")
   329  	c.Assert(err, check.IsNil)
   330  	defer failpoint.Disable("github.com/pingcap/ticdc/cdc/puller/sorter/getMemoryPressureFails") //nolint:errcheck
   331  
   332  	dir := c.MkDir()
   333  	backEndPool, err := newBackEndPool(dir, "")
   334  	c.Assert(err, check.IsNil)
   335  	c.Assert(backEndPool, check.NotNil)
   336  	defer backEndPool.terminate()
   337  
   338  	after := time.After(time.Second * 20)
   339  	tick := time.Tick(time.Second * 1)
   340  	for {
   341  		select {
   342  		case <-after:
   343  			c.Fatal("TestGetMemoryPressureFailure timed out")
   344  		case <-tick:
   345  			if backEndPool.memoryPressure() == 100 {
   346  				return
   347  			}
   348  		}
   349  	}
   350  }